From 8a2052280f737ad57816c5e34cd5f1373b842009 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 15:54:51 -0400 Subject: [PATCH 01/21] Initial formatter crate --- Cargo.lock | 14 + Cargo.toml | 1 + .../src/std/conditions/message_flags.rue | 6 + .../src/std/conditions/opcodes.rue | 34 + .../rue-compiler/src/std/conditions/types.rue | 49 +- .../rue-compiler/src/std/curry_tree_hash.rue | 25 +- crates/rue-compiler/src/std/merkle.rue | 18 +- crates/rue-compiler/src/std/prelude.rue | 6 + crates/rue-compiler/src/std/utils.rue | 3 +- crates/rue-formatter/Cargo.toml | 26 + crates/rue-formatter/src/document.rs | 82 +++ crates/rue-formatter/src/format.rs | 674 ++++++++++++++++++ crates/rue-formatter/src/lib.rs | 173 +++++ crates/rue-formatter/src/renderer.rs | 264 +++++++ crates/rue-formatter/src/tests.rs | 263 +++++++ crates/rue-formatter/src/token_stream.rs | 134 ++++ crates/rue-tests/Cargo.toml | 1 + crates/rue-tests/src/main.rs | 63 ++ examples/brainfck.rue | 118 +-- examples/brainfck.yaml | 4 +- examples/fizz_buzz.rue | 24 +- examples/puzzles/cat.rue | 49 +- examples/puzzles/cat.yaml | 2 +- examples/puzzles/p2_delegated_conditions.rue | 5 +- examples/puzzles/partial_offer.rue | 50 +- examples/puzzles/partial_offer.yaml | 2 +- examples/puzzles/royalty_split.rue | 7 +- examples/puzzles/royalty_split.yaml | 2 +- examples/puzzles/singleton.rue | 109 ++- examples/puzzles/singleton.yaml | 4 +- tests/blocks/if_stmt.rue | 3 +- tests/builtins.rue | 26 +- tests/builtins.yaml | 14 +- tests/expressions/blocks.rue | 28 +- tests/expressions/const_expr.rue | 39 +- tests/expressions/const_expr_errors.rue | 8 +- tests/expressions/const_expr_errors.yaml | 4 +- tests/expressions/const_expr_unresolved.rue | 4 +- tests/expressions/const_expr_unresolved.yaml | 2 +- tests/expressions/if_else.rue | 12 +- tests/external/external_main.rue | 3 +- tests/external/signature_types.rue | 7 +- tests/imports/duplicate_imports.rue | 2 +- tests/imports/import_order.rue | 1 + tests/imports/unresolved_imports.rue | 2 + tests/imports/unresolved_imports.yaml | 2 +- tests/optimizer/function_value.rue | 2 +- tests/projects/external_import/main.rue | 6 +- .../external_import/nested/helper.rue | 3 +- tests/std.rue | 34 +- tests/std.yaml | 8 +- tests/stress/many_params_binary_tree.rue | 553 +++++++++++++- tests/stress/many_params_binary_tree.yaml | 2 +- tests/stress/many_params_non_overflow.rue | 116 ++- tests/stress/many_params_non_overflow.yaml | 8 +- tests/symbols/closures.rue | 1 + tests/symbols/closures.yaml | 2 +- tests/symbols/complex_constants.rue | 3 + tests/symbols/inline_constants.rue | 1 + tests/symbols/inline_functions.rue | 1 - tests/symbols/invalid_recursion.rue | 3 + tests/symbols/invalid_recursion.yaml | 6 +- tests/symbols/super_errors.rue | 2 + tests/symbols/super_errors.yaml | 10 +- tests/types/struct_error.rue | 5 +- tests/types/struct_error.yaml | 2 +- tests/warnings/always_condition.rue | 2 +- tests/warnings/empty_generic_parameters.rue | 2 +- tests/warnings/unnecessary_guard.rue | 1 - tests/warnings/unused_imports.rue | 9 + tests/warnings/unused_imports.yaml | 14 +- tests/warnings/unused_symbols.rue | 3 + tests/warnings/unused_symbols.yaml | 4 +- tests/warnings/unused_types.rue | 6 + tests/warnings/unused_types.yaml | 8 +- 75 files changed, 2801 insertions(+), 385 deletions(-) create mode 100644 crates/rue-formatter/Cargo.toml create mode 100644 crates/rue-formatter/src/document.rs create mode 100644 crates/rue-formatter/src/format.rs create mode 100644 crates/rue-formatter/src/lib.rs create mode 100644 crates/rue-formatter/src/renderer.rs create mode 100644 crates/rue-formatter/src/tests.rs create mode 100644 crates/rue-formatter/src/token_stream.rs diff --git a/Cargo.lock b/Cargo.lock index 3362d850..86066dc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1768,6 +1768,19 @@ dependencies = [ "thiserror 2.0.14", ] +[[package]] +name = "rue-formatter" +version = "0.9.0" +dependencies = [ + "expect-test", + "rowan", + "rue-ast", + "rue-diagnostic", + "rue-lexer", + "rue-parser", + "thiserror 2.0.14", +] + [[package]] name = "rue-hir" version = "0.9.0" @@ -1860,6 +1873,7 @@ dependencies = [ "indexmap", "rue-compiler", "rue-diagnostic", + "rue-formatter", "rue-lir", "rue-options", "serde", diff --git a/Cargo.toml b/Cargo.toml index 525500d9..7ce2fb2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ rue-lexer = { path = "crates/rue-lexer", version = "0.9.0" } rue-parser = { path = "crates/rue-parser", version = "0.9.0" } rue-diagnostic = { path = "crates/rue-diagnostic", version = "0.9.0" } rue-ast = { path = "crates/rue-ast", version = "0.9.0" } +rue-formatter = { path = "crates/rue-formatter", version = "0.9.0" } rue-compiler = { path = "crates/rue-compiler", version = "0.9.0" } rue-options = { path = "crates/rue-options", version = "0.9.0" } rue-lir = { path = "crates/rue-lir", version = "0.9.0" } diff --git a/crates/rue-compiler/src/std/conditions/message_flags.rue b/crates/rue-compiler/src/std/conditions/message_flags.rue index db5f501f..8c014d99 100644 --- a/crates/rue-compiler/src/std/conditions/message_flags.rue +++ b/crates/rue-compiler/src/std/conditions/message_flags.rue @@ -1,9 +1,15 @@ export inline const SENDER_COIN: 0b111_000 = 0b111_000; + export inline const SENDER_PARENT: 0b100_000 = 0b100_000; + export inline const SENDER_PUZZLE: 0b010_000 = 0b010_000; + export inline const SENDER_AMOUNT: 0b001_000 = 0b001_000; export inline const RECEIVER_COIN: 0b000_111 = 0b000_111; + export inline const RECEIVER_PARENT: 0b000_100 = 0b000_100; + export inline const RECEIVER_PUZZLE: 0b000_010 = 0b000_010; + export inline const RECEIVER_AMOUNT: 0b000_001 = 0b000_001; diff --git a/crates/rue-compiler/src/std/conditions/opcodes.rue b/crates/rue-compiler/src/std/conditions/opcodes.rue index 309ab18f..4d87d14a 100644 --- a/crates/rue-compiler/src/std/conditions/opcodes.rue +++ b/crates/rue-compiler/src/std/conditions/opcodes.rue @@ -1,35 +1,69 @@ export inline const REMARK: 1 = 1; + export inline const AGG_SIG_PARENT: 43 = 43; + export inline const AGG_SIG_PUZZLE: 44 = 44; + export inline const AGG_SIG_AMOUNT: 45 = 45; + export inline const AGG_SIG_PUZZLE_AMOUNT: 46 = 46; + export inline const AGG_SIG_PARENT_AMOUNT: 47 = 47; + export inline const AGG_SIG_PARENT_PUZZLE: 48 = 48; + export inline const AGG_SIG_UNSAFE: 49 = 49; + export inline const AGG_SIG_ME: 50 = 50; + export inline const CREATE_COIN: 51 = 51; + export inline const RESERVE_FEE: 52 = 52; + export inline const CREATE_COIN_ANNOUNCEMENT: 60 = 60; + export inline const ASSERT_COIN_ANNOUNCEMENT: 61 = 61; + export inline const CREATE_PUZZLE_ANNOUNCEMENT: 62 = 62; + export inline const ASSERT_PUZZLE_ANNOUNCEMENT: 63 = 63; + export inline const ASSERT_CONCURRENT_SPEND: 64 = 64; + export inline const ASSERT_CONCURRENT_PUZZLE: 65 = 65; + export inline const SEND_MESSAGE: 66 = 66; + export inline const RECEIVE_MESSAGE: 67 = 67; + export inline const ASSERT_MY_COIN_ID: 70 = 70; + export inline const ASSERT_MY_PARENT_ID: 71 = 71; + export inline const ASSERT_MY_PUZZLE_HASH: 72 = 72; + export inline const ASSERT_MY_AMOUNT: 73 = 73; + export inline const ASSERT_MY_BIRTH_SECONDS: 74 = 74; + export inline const ASSERT_MY_BIRTH_HEIGHT: 75 = 75; + export inline const ASSERT_EPHEMERAL: 76 = 76; + export inline const ASSERT_SECONDS_RELATIVE: 80 = 80; + export inline const ASSERT_SECONDS_ABSOLUTE: 81 = 81; + export inline const ASSERT_HEIGHT_RELATIVE: 82 = 82; + export inline const ASSERT_HEIGHT_ABSOLUTE: 83 = 83; + export inline const ASSERT_BEFORE_SECONDS_RELATIVE: 84 = 84; + export inline const ASSERT_BEFORE_SECONDS_ABSOLUTE: 85 = 85; + export inline const ASSERT_BEFORE_HEIGHT_RELATIVE: 86 = 86; + export inline const ASSERT_BEFORE_HEIGHT_ABSOLUTE: 87 = 87; + export inline const SOFTFORK: 90 = 90; diff --git a/crates/rue-compiler/src/std/conditions/types.rue b/crates/rue-compiler/src/std/conditions/types.rue index 621f247c..e9fd5bdb 100644 --- a/crates/rue-compiler/src/std/conditions/types.rue +++ b/crates/rue-compiler/src/std/conditions/types.rue @@ -193,17 +193,38 @@ export struct Softfork { ...args: List, } -export type Condition = - Remark | AggSigParent | AggSigPuzzle | AggSigAmount - | AggSigPuzzleAmount | AggSigParentAmount | AggSigParentPuzzle - | AggSigUnsafe | AggSigMe | CreateCoin | ReserveFee - | CreateCoinAnnouncement | AssertCoinAnnouncement - | CreatePuzzleAnnouncement | AssertPuzzleAnnouncement - | AssertConcurrentSpend | AssertConcurrentPuzzle - | SendMessage | ReceiveMessage | AssertMyCoinId | AssertMyParentId - | AssertMyPuzzleHash | AssertMyAmount | AssertMyBirthSeconds - | AssertMyBirthHeight | AssertEphemeral | AssertSecondsRelative - | AssertSecondsAbsolute | AssertHeightRelative | AssertHeightAbsolute - | AssertBeforeSecondsRelative | AssertBeforeSecondsAbsolute - | AssertBeforeHeightRelative | AssertBeforeHeightAbsolute - | Softfork; +export type Condition = Remark | + AggSigParent | + AggSigPuzzle | + AggSigAmount | + AggSigPuzzleAmount | + AggSigParentAmount | + AggSigParentPuzzle | + AggSigUnsafe | + AggSigMe | + CreateCoin | + ReserveFee | + CreateCoinAnnouncement | + AssertCoinAnnouncement | + CreatePuzzleAnnouncement | + AssertPuzzleAnnouncement | + AssertConcurrentSpend | + AssertConcurrentPuzzle | + SendMessage | + ReceiveMessage | + AssertMyCoinId | + AssertMyParentId | + AssertMyPuzzleHash | + AssertMyAmount | + AssertMyBirthSeconds | + AssertMyBirthHeight | + AssertEphemeral | + AssertSecondsRelative | + AssertSecondsAbsolute | + AssertHeightRelative | + AssertHeightAbsolute | + AssertBeforeSecondsRelative | + AssertBeforeSecondsAbsolute | + AssertBeforeHeightRelative | + AssertBeforeHeightAbsolute | + Softfork; diff --git a/crates/rue-compiler/src/std/curry_tree_hash.rue b/crates/rue-compiler/src/std/curry_tree_hash.rue index ace01785..e7fc472e 100644 --- a/crates/rue-compiler/src/std/curry_tree_hash.rue +++ b/crates/rue-compiler/src/std/curry_tree_hash.rue @@ -9,26 +9,27 @@ inline fn two_item_list_hash(first: Bytes32, rest: Bytes32) -> Bytes32 { } fn apply_hash(mod_hash: Bytes32, environment_hash: Bytes32) -> Bytes32 { - sha256(2 as Bytes + tree_hash_atom(2 as Bytes) + two_item_list_hash(quote_hash(mod_hash), environment_hash)) + sha256( + 2 as Bytes + + tree_hash_atom(2 as Bytes) + + two_item_list_hash(quote_hash(mod_hash), environment_hash) + ) } -fn update_hash_with_parameter( - parameter_hash: Bytes32, - environment_hash: Bytes32 -) -> Bytes32 { - sha256(2 as Bytes + tree_hash_atom(4 as Bytes) + two_item_list_hash(quote_hash(parameter_hash), environment_hash)) +fn update_hash_with_parameter(parameter_hash: Bytes32, environment_hash: Bytes32) -> Bytes32 { + sha256( + 2 as Bytes + + tree_hash_atom(4 as Bytes) + + two_item_list_hash(quote_hash(parameter_hash), environment_hash) + ) } fn curried_params_hash(parameters: List) -> Bytes32 { if parameters is nil { return tree_hash_atom(1 as Bytes); - } - update_hash_with_parameter(parameters.first, curried_params_hash(parameters.rest)) + } update_hash_with_parameter(parameters.first, curried_params_hash(parameters.rest)) } -export fn curry_tree_hash( - mod_hash: Bytes32, - parameters: List -) -> Bytes32 { +export fn curry_tree_hash(mod_hash: Bytes32, parameters: List) -> Bytes32 { apply_hash(mod_hash, curried_params_hash(parameters)) } diff --git a/crates/rue-compiler/src/std/merkle.rue b/crates/rue-compiler/src/std/merkle.rue index 1414f431..1b8f69b0 100644 --- a/crates/rue-compiler/src/std/merkle.rue +++ b/crates/rue-compiler/src/std/merkle.rue @@ -1,5 +1,4 @@ // merkle.rue by yakuhito - import tree_hash::{tree_hash_atom, tree_hash_pair}; export struct MerkleProof { @@ -15,7 +14,7 @@ export fn calculate_merkle_root( if additional_steps is nil { return current_hash; } - + calculate_merkle_root( path >>> 1, tree_hash_pair( @@ -34,10 +33,7 @@ export fn calculate_merkle_root( ) } -fn simplify_merkle_proof_after_leaf( - leaf_hash: Bytes32, - proof: MerkleProof, -) -> Bytes32 { +fn simplify_merkle_proof_after_leaf(leaf_hash: Bytes32, proof: MerkleProof,) -> Bytes32 { if proof.hashes is nil { return leaf_hash; } @@ -55,16 +51,10 @@ fn simplify_merkle_proof_after_leaf( proof.hashes.first } ), - MerkleProof { - path: proof.path >>> 1, - hashes: proof.hashes.rest - } + MerkleProof { path: proof.path >>> 1, hashes: proof.hashes.rest } ) } -export inline fn simplify_merkle_proof( - leaf: Bytes32, - proof: MerkleProof, -) -> Bytes32 { +export inline fn simplify_merkle_proof(leaf: Bytes32, proof: MerkleProof,) -> Bytes32 { simplify_merkle_proof_after_leaf(tree_hash_atom(leaf), proof) } diff --git a/crates/rue-compiler/src/std/prelude.rue b/crates/rue-compiler/src/std/prelude.rue index 3328546a..5d779246 100644 --- a/crates/rue-compiler/src/std/prelude.rue +++ b/crates/rue-compiler/src/std/prelude.rue @@ -1,9 +1,15 @@ export utils::*; + export tree_hash::*; + export curry_tree_hash::*; + export conditions::message_flags::*; + export conditions::opcodes::*; + export conditions::types::*; + export merkle::*; export mod std { diff --git a/crates/rue-compiler/src/std/utils.rue b/crates/rue-compiler/src/std/utils.rue index 67d0cff5..239a346e 100644 --- a/crates/rue-compiler/src/std/utils.rue +++ b/crates/rue-compiler/src/std/utils.rue @@ -1,8 +1,7 @@ export fn merge_list(a: List, b: List) -> List { if a is (T, List) { return [a.first, ...merge_list(a.rest, b)]; - } - b + } b } export fn deep_equal(a: Any, b: Any) -> Bool { diff --git a/crates/rue-formatter/Cargo.toml b/crates/rue-formatter/Cargo.toml new file mode 100644 index 00000000..193384dc --- /dev/null +++ b/crates/rue-formatter/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "rue-formatter" +version = "0.9.0" +edition = "2024" +license = "Apache-2.0" +description = "An AST formatter for the Rue programming language." +authors = ["Brandon Haggstrom "] +homepage = "https://github.com/xch-dev/rue" +repository = "https://github.com/xch-dev/rue" +readme = { workspace = true } +keywords = { workspace = true } +categories = { workspace = true } + +[lints] +workspace = true + +[dependencies] +rue-ast = { workspace = true } +rue-diagnostic = { workspace = true } +rue-lexer = { workspace = true } +rue-parser = { workspace = true } +rowan = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +expect-test = { workspace = true } diff --git a/crates/rue-formatter/src/document.rs b/crates/rue-formatter/src/document.rs new file mode 100644 index 00000000..95546bd0 --- /dev/null +++ b/crates/rue-formatter/src/document.rs @@ -0,0 +1,82 @@ +//! A deliberately small, private pretty-printing document model. + +#[derive(Debug, Clone)] +pub(crate) enum Doc { + Nil, + Text(String), + Concat(Vec), + Line(LineKind), + Indent(Box), + Group(Box), + PreferredBreak { + doc: Box, + indent_on_break: bool, + }, + IfBreak { + broken: Box, + flat: Box, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum LineKind { + Soft, + Hard, + Empty, +} + +impl Doc { + pub(crate) fn text(text: impl Into) -> Self { + Self::Text(text.into()) + } + + pub(crate) fn concat(docs: impl IntoIterator) -> Self { + let mut flattened = Vec::new(); + for doc in docs { + match doc { + Self::Nil => {} + Self::Concat(children) => flattened.extend(children), + doc => flattened.push(doc), + } + } + Self::Concat(flattened) + } + + pub(crate) fn space() -> Self { + Self::text(" ") + } + + pub(crate) fn soft_line() -> Self { + Self::Line(LineKind::Soft) + } + + pub(crate) fn hard_line() -> Self { + Self::Line(LineKind::Hard) + } + + pub(crate) fn empty_line() -> Self { + Self::Line(LineKind::Empty) + } + + pub(crate) fn indent(self) -> Self { + Self::Indent(Box::new(self)) + } + + pub(crate) fn group(self) -> Self { + Self::Group(Box::new(self)) + } + + pub(crate) fn preferred_break(doc: Self, indent_on_break: bool) -> Self { + Self::PreferredBreak { + doc: Box::new(doc), + indent_on_break, + } + } + + pub(crate) fn if_break(broken: Self, flat: Self) -> Self { + Self::IfBreak { + broken: Box::new(broken), + flat: Box::new(flat), + } + } +} diff --git a/crates/rue-formatter/src/format.rs b/crates/rue-formatter/src/format.rs new file mode 100644 index 00000000..d0add877 --- /dev/null +++ b/crates/rue-formatter/src/format.rs @@ -0,0 +1,674 @@ +use std::collections::{HashMap, HashSet}; + +use rue_ast::{AstDocument, AstNode}; +use rue_parser::{SyntaxKind, SyntaxNode, T}; + +use crate::{ + FormatError, + document::Doc, + token_stream::{Comment, Gap, TokenStream}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DelimiterStyle { + Block, + Braced, + Group, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Separator { + None, + Space, + Soft, + Hard, + Empty, +} + +#[derive(Debug)] +struct Layout { + pairs: HashMap, + block_openers: HashSet, + braced_openers: HashSet, + generic_tokens: HashSet, + prefix_operators: HashSet, + attached_openers: HashSet, + item_ends: HashSet, + groups: HashMap, + binary_operators: HashMap>, +} + +pub(crate) fn format_document( + document: &AstDocument, + stream: &TokenStream, +) -> Result { + validate_node_kinds(document.syntax())?; + let layout = Layout::new(document, stream)?; + let mut formatter = Formatter { + stream, + layout, + consumed_tokens: 0, + consumed_comments: 0, + }; + + let mut docs = vec![formatter.gap_doc(&stream.gaps[0], Separator::None)]; + docs.push(formatter.span(0, stream.tokens.len())?); + if !stream.tokens.is_empty() { + docs.push(formatter.gap_doc( + stream.gaps.last().expect("a trailing gap always exists"), + Separator::None, + )); + } + + if formatter.consumed_tokens != stream.tokens.len() { + return Err(FormatError::Internal(format!( + "emitted {} of {} significant tokens", + formatter.consumed_tokens, + stream.tokens.len() + ))); + } + if formatter.consumed_comments != stream.comment_count { + return Err(FormatError::Internal(format!( + "emitted {} of {} comments", + formatter.consumed_comments, stream.comment_count + ))); + } + + Ok(Doc::concat(docs)) +} + +#[derive(Debug)] +struct Formatter<'a> { + stream: &'a TokenStream, + layout: Layout, + consumed_tokens: usize, + consumed_comments: usize, +} + +impl Formatter<'_> { + fn span(&mut self, start: usize, end: usize) -> Result { + self.span_inner(start, end, true) + } + + fn span_inner( + &mut self, + start: usize, + end: usize, + allow_outer_group: bool, + ) -> Result { + if allow_outer_group + && self + .layout + .groups + .get(&start) + .is_some_and(|group_end| *group_end + 1 == end) + { + return self.grouped_span(start, end); + } + + let mut docs = Vec::new(); + let mut index = start; + + while index < end { + let atom_end = if let Some(&group_end) = self.layout.groups.get(&index) + && group_end < end + && !(index == start && group_end + 1 == end) + { + docs.push(self.grouped_span(index, group_end + 1)?); + group_end + } else if let Some(&close) = self.layout.pairs.get(&index) { + if close >= end { + return Err(FormatError::Internal( + "delimiter pair crosses formatting span".to_string(), + )); + } + docs.push(self.delimited(index, close)?); + close + } else { + docs.push(self.token(index)); + index + }; + + index = atom_end + 1; + if index < end { + let separator = self.separator(atom_end, index); + docs.push(self.gap_doc(&self.stream.gaps[index], separator)); + } + } + + Ok(Doc::concat(docs)) + } + + fn grouped_span(&mut self, start: usize, end: usize) -> Result { + if self.layout.binary_operators.contains_key(&start) { + return Ok(self.binary_span(start, end)?.group()); + } + Ok(self.span_inner(start, end, false)?.indent().group()) + } + + fn binary_span(&mut self, start: usize, end: usize) -> Result { + let operators = self + .layout + .binary_operators + .get(&start) + .cloned() + .unwrap_or_default(); + + let Some(&first_operator) = operators.first() else { + return self.span_inner(start, end, false); + }; + + let mut docs = vec![self.span_inner(start, first_operator, false)?]; + for (position, operator) in operators.iter().enumerate() { + let operand_start = operator + 1; + let segment_end = operators.get(position + 1).copied().unwrap_or(end); + let operator_doc = self.token(*operator); + let after_operator = self.gap_doc(&self.stream.gaps[operand_start], Separator::Space); + let operand = self.span_inner(operand_start, segment_end, false)?; + let segment = Doc::concat([operator_doc, after_operator, operand]); + if is_comparison_operator(self.stream.tokens[*operator].kind) + && self.stream.gaps[*operator].comments.is_empty() + { + let preferred = Doc::preferred_break(segment, position == 0); + docs.push(if position == 0 { + preferred + } else { + preferred.indent() + }); + } else { + let before_operator = self.gap_doc(&self.stream.gaps[*operator], Separator::Soft); + docs.push(Doc::concat([before_operator, segment]).indent()); + } + } + Ok(Doc::concat(docs)) + } + + fn delimited(&mut self, open: usize, close: usize) -> Result { + let style = if self + .layout + .block_openers + .contains(&self.stream.tokens[open].start) + { + DelimiterStyle::Block + } else if self + .layout + .braced_openers + .contains(&self.stream.tokens[open].start) + { + DelimiterStyle::Braced + } else { + DelimiterStyle::Group + }; + let open_doc = self.token(open); + let close_doc = self.token(close); + let inner_start = open + 1; + + if inner_start == close { + let gap = self.gap_doc_with_comments( + &self.stream.gaps[inner_start], + if self.stream.gaps[inner_start].comments.is_empty() { + Separator::None + } else if style == DelimiterStyle::Block { + Separator::Hard + } else { + Separator::Soft + }, + true, + ); + return Ok(Doc::concat([open_doc, gap, close_doc]).group()); + } + + let inner = self.span(inner_start, close)?; + let leading_gap = &self.stream.gaps[inner_start]; + let trailing_gap = &self.stream.gaps[close]; + let leading_comment_starts_line = leading_gap + .comments + .first() + .is_some_and(|comment| comment.newlines_before > 0 || comment.multiline); + let trailing_comment_ends_line = trailing_gap.comments.last().is_some_and(|comment| { + comment.kind == SyntaxKind::LineComment + || comment.multiline + || trailing_gap.newlines > 0 + }); + let leading = self.gap_doc_with_comments( + leading_gap, + match style { + DelimiterStyle::Block => Separator::Hard, + DelimiterStyle::Braced => Separator::Soft, + DelimiterStyle::Group => Separator::None, + }, + true, + ); + let trailing = self.gap_doc( + trailing_gap, + match style { + DelimiterStyle::Block => Separator::Hard, + DelimiterStyle::Braced => Separator::Soft, + DelimiterStyle::Group => Separator::None, + }, + ); + + Ok(match style { + DelimiterStyle::Block => Doc::concat([ + open_doc, + Doc::concat([leading, inner]).indent(), + trailing, + close_doc, + ]), + DelimiterStyle::Braced => Doc::concat([ + open_doc, + Doc::concat([leading, inner]).indent(), + trailing, + close_doc, + ]) + .group(), + DelimiterStyle::Group => Doc::concat([ + open_doc, + Doc::concat([ + if leading_comment_starts_line { + Doc::Nil + } else { + Doc::if_break(Doc::hard_line(), Doc::Nil) + }, + leading, + inner, + ]) + .indent(), + trailing, + if trailing_comment_ends_line { + Doc::Nil + } else { + Doc::if_break(Doc::hard_line(), Doc::Nil) + }, + close_doc, + ]) + .group(), + }) + } + + fn token(&mut self, index: usize) -> Doc { + self.consumed_tokens += 1; + Doc::text(self.stream.tokens[index].text.clone()) + } + + fn separator(&self, previous: usize, next: usize) -> Separator { + let left = &self.stream.tokens[previous]; + let right = &self.stream.tokens[next]; + + if self.layout.item_ends.contains(&left.start) { + return Separator::Empty; + } + + if left.kind == T![;] { + return Separator::Hard; + } + if left.kind == T![,] { + return Separator::Soft; + } + if no_space_after(left.kind) || no_space_before(right.kind) { + return Separator::None; + } + if SyntaxKind::BINARY_OPS.contains(&left.kind) + && !self.layout.generic_tokens.contains(&left.start) + && !self.layout.prefix_operators.contains(&left.start) + { + return Separator::Soft; + } + if right.kind == T!['('] && self.layout.attached_openers.contains(&right.start) { + return Separator::None; + } + if left.kind == T!['}'] && right.kind == T![else] { + return Separator::Space; + } + + if ((left.kind == T![<] + || right.kind == T![<] + || right.kind == T![>] + || (left.kind == T![>] && right.kind == T!['('])) + && (self.layout.generic_tokens.contains(&left.start) + || self.layout.generic_tokens.contains(&right.start))) + || (SyntaxKind::PREFIX_OPS.contains(&left.kind) + && self.layout.prefix_operators.contains(&left.start)) + { + Separator::None + } else { + Separator::Space + } + } + + fn gap_doc(&mut self, gap: &Gap, requested: Separator) -> Doc { + self.gap_doc_with_comments(gap, requested, false) + } + + fn gap_doc_with_comments( + &mut self, + gap: &Gap, + requested: Separator, + ignore_trailing: bool, + ) -> Doc { + if gap.comments.is_empty() { + if gap.newlines > 1 && requested != Separator::None { + return Doc::empty_line(); + } + return separator_doc(requested); + } + + let mut docs = Vec::new(); + for (index, comment) in gap.comments.iter().enumerate() { + self.consumed_comments += 1; + let before = if index == 0 { + comment_separator(comment, requested, ignore_trailing) + } else { + comment_separator(comment, Separator::Hard, false) + }; + docs.push(before); + docs.push(Doc::text(comment.text.clone())); + + let followed_inline = match gap.comments.get(index + 1) { + Some(next) => next.newlines_before == 0, + None => gap.newlines == 0, + }; + if comment.kind == SyntaxKind::BlockComment && !comment.multiline && followed_inline { + docs.push(Doc::space()); + } + } + + let last = gap.comments.last().expect("comments are not empty"); + if gap.newlines > 1 { + docs.push(Doc::empty_line()); + } else if last.kind == SyntaxKind::LineComment || last.multiline || gap.newlines > 0 { + docs.push(Doc::hard_line()); + } + Doc::concat(docs) + } +} + +impl Layout { + fn new(document: &AstDocument, stream: &TokenStream) -> Result { + let mut pairs = HashMap::new(); + let mut stack: Vec<(SyntaxKind, usize)> = Vec::new(); + for (index, token) in stream.tokens.iter().enumerate() { + match token.kind { + T!['('] | T!['['] | T!['{'] => stack.push((token.kind, index)), + T![')'] | T![']'] | T!['}'] => { + let Some((open, open_index)) = stack.pop() else { + return Err(FormatError::Internal( + "closing delimiter has no opener".to_string(), + )); + }; + if !delimiters_match(open, token.kind) { + return Err(FormatError::Internal( + "mismatched delimiters in valid syntax".to_string(), + )); + } + pairs.insert(open_index, index); + } + _ => {} + } + } + if !stack.is_empty() { + return Err(FormatError::Internal( + "opening delimiter has no closer".to_string(), + )); + } + + let root = document.syntax(); + let mut block_openers = HashSet::new(); + let mut braced_openers = HashSet::new(); + let mut generic_tokens = HashSet::new(); + let mut prefix_operators = HashSet::new(); + let mut attached_openers = HashSet::new(); + + for node in root.descendants() { + match node.kind() { + SyntaxKind::Block | SyntaxKind::ModuleItem | SyntaxKind::StructItem => { + if let Some(token) = + significant_tokens(&node).find(|token| token.kind() == T!['{']) + { + block_openers.insert(usize::from(token.text_range().start())); + } + } + SyntaxKind::StructInitializerExpr | SyntaxKind::StructBinding => { + if let Some(token) = + significant_tokens(&node).find(|token| token.kind() == T!['{']) + { + braced_openers.insert(usize::from(token.text_range().start())); + } + } + SyntaxKind::GenericParameters | SyntaxKind::GenericArguments => { + generic_tokens.extend( + significant_tokens(&node) + .filter(|token| is_generic_punctuation(token.kind())) + .map(|token| usize::from(token.text_range().start())), + ); + } + SyntaxKind::PrefixExpr => { + if let Some(token) = significant_tokens(&node) + .find(|token| SyntaxKind::PREFIX_OPS.contains(&token.kind())) + { + prefix_operators.insert(usize::from(token.text_range().start())); + } + } + SyntaxKind::FunctionItem + | SyntaxKind::FunctionCallExpr + | SyntaxKind::LambdaExpr + | SyntaxKind::LambdaType => { + if let Some(token) = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .find(|token| token.kind() == T!['(']) + { + attached_openers.insert(usize::from(token.text_range().start())); + } + } + _ => {} + } + } + + let mut item_ends = HashSet::new(); + for item in document.items() { + if let Some(token) = significant_tokens(item.syntax()).last() { + let token_start = usize::from(token.text_range().start()); + let item_end = stream + .tokens + .iter() + .position(|candidate| candidate.start == token_start) + .and_then(|index| stream.tokens.get(index + 1)) + .filter(|next| next.kind == T![;]) + .map_or(token_start, |semicolon| semicolon.start); + item_ends.insert(item_end); + } + } + + let token_indices: HashMap = stream + .tokens + .iter() + .enumerate() + .map(|(index, token)| (token.start, index)) + .collect(); + let mut groups = HashMap::new(); + let mut binary_operators = HashMap::new(); + for node in root + .descendants() + .filter(|node| matches!(node.kind(), SyntaxKind::BinaryExpr | SyntaxKind::UnionType)) + .filter(|node| { + node.parent() + .is_none_or(|parent| parent.kind() != node.kind()) + }) + { + let mut tokens = significant_tokens(&node); + let Some(first) = tokens.next() else { + continue; + }; + let last = tokens.last().unwrap_or_else(|| first.clone()); + let Some(&start) = token_indices.get(&usize::from(first.text_range().start())) else { + continue; + }; + let Some(&end) = token_indices.get(&usize::from(last.text_range().start())) else { + continue; + }; + groups + .entry(start) + .and_modify(|current: &mut usize| *current = (*current).max(end)) + .or_insert(end); + if node.kind() == SyntaxKind::BinaryExpr { + let mut operators = Vec::new(); + collect_binary_operator_starts(&node, &mut operators); + binary_operators.insert( + start, + operators + .into_iter() + .filter_map(|operator| token_indices.get(&operator).copied()) + .collect(), + ); + } + } + + Ok(Self { + pairs, + block_openers, + braced_openers, + generic_tokens, + prefix_operators, + attached_openers, + item_ends, + groups, + binary_operators, + }) + } +} + +fn collect_binary_operator_starts(node: &SyntaxNode, operators: &mut Vec) { + for element in node.children_with_tokens() { + match element { + rowan::NodeOrToken::Node(child) if child.kind() == SyntaxKind::BinaryExpr => { + collect_binary_operator_starts(&child, operators); + } + rowan::NodeOrToken::Token(token) if SyntaxKind::BINARY_OPS.contains(&token.kind()) => { + operators.push(usize::from(token.text_range().start())); + } + _ => {} + } + } +} + +fn significant_tokens(node: &SyntaxNode) -> impl Iterator + '_ { + node.descendants_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .filter(|token| !token.kind().is_trivia()) +} + +fn separator_doc(separator: Separator) -> Doc { + match separator { + Separator::None => Doc::Nil, + Separator::Space => Doc::space(), + Separator::Soft => Doc::soft_line(), + Separator::Hard => Doc::hard_line(), + Separator::Empty => Doc::empty_line(), + } +} + +fn comment_separator(comment: &Comment, requested: Separator, ignore_trailing: bool) -> Doc { + if comment.trailing && !ignore_trailing { + return Doc::space(); + } + if comment.newlines_before > 1 { + return Doc::empty_line(); + } + if comment.newlines_before > 0 || comment.multiline { + return Doc::hard_line(); + } + separator_doc(match requested { + Separator::None => Separator::None, + requested => requested, + }) +} + +fn no_space_after(kind: SyntaxKind) -> bool { + matches!(kind, T!['('] | T!['['] | T![::] | T![.] | T![...] | T![,]) +} + +fn no_space_before(kind: SyntaxKind) -> bool { + matches!( + kind, + T![')'] | T![']'] | T![,] | T![;] | T![::] | T![.] | T![:] + ) +} + +fn is_generic_punctuation(kind: SyntaxKind) -> bool { + matches!(kind, T![<] | T![>] | T![,]) +} + +fn is_comparison_operator(kind: SyntaxKind) -> bool { + matches!(kind, T![==] | T![!=] | T![<] | T![>] | T![<=] | T![>=]) +} + +fn delimiters_match(open: SyntaxKind, close: SyntaxKind) -> bool { + matches!( + (open, close), + (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) + ) +} + +fn validate_node_kinds(root: &SyntaxNode) -> Result<(), FormatError> { + for node in root.descendants() { + match node.kind() { + SyntaxKind::Document + | SyntaxKind::ModuleItem + | SyntaxKind::FunctionItem + | SyntaxKind::FunctionParameter + | SyntaxKind::ConstantItem + | SyntaxKind::TypeAliasItem + | SyntaxKind::StructItem + | SyntaxKind::StructField + | SyntaxKind::ImportItem + | SyntaxKind::ImportPath + | SyntaxKind::ImportPathSegment + | SyntaxKind::GenericParameters + | SyntaxKind::GenericArguments + | SyntaxKind::LiteralType + | SyntaxKind::PathType + | SyntaxKind::UnionType + | SyntaxKind::GroupType + | SyntaxKind::PairType + | SyntaxKind::ListType + | SyntaxKind::ListTypeItem + | SyntaxKind::LambdaType + | SyntaxKind::LambdaParameter + | SyntaxKind::Block + | SyntaxKind::LetStmt + | SyntaxKind::ExprStmt + | SyntaxKind::IfStmt + | SyntaxKind::ReturnStmt + | SyntaxKind::AssertStmt + | SyntaxKind::RaiseStmt + | SyntaxKind::DebugStmt + | SyntaxKind::PathExpr + | SyntaxKind::PathSegment + | SyntaxKind::StructInitializerExpr + | SyntaxKind::StructInitializerField + | SyntaxKind::LiteralExpr + | SyntaxKind::ConstExpr + | SyntaxKind::GroupExpr + | SyntaxKind::PairExpr + | SyntaxKind::ListExpr + | SyntaxKind::ListItem + | SyntaxKind::PrefixExpr + | SyntaxKind::BinaryExpr + | SyntaxKind::FunctionCallExpr + | SyntaxKind::IfExpr + | SyntaxKind::GuardExpr + | SyntaxKind::CastExpr + | SyntaxKind::FieldAccessExpr + | SyntaxKind::LambdaExpr + | SyntaxKind::NamedBinding + | SyntaxKind::PairBinding + | SyntaxKind::ListBinding + | SyntaxKind::ListBindingItem + | SyntaxKind::StructBinding + | SyntaxKind::StructFieldBinding => {} + kind => return Err(FormatError::UnsupportedSyntax(kind)), + } + } + Ok(()) +} diff --git a/crates/rue-formatter/src/lib.rs b/crates/rue-formatter/src/lib.rs new file mode 100644 index 00000000..bcca5a1f --- /dev/null +++ b/crates/rue-formatter/src/lib.rs @@ -0,0 +1,173 @@ +//! Canonical source formatter for Rue. +//! +//! The formatter uses Rue's typed AST for syntax structure and its lossless +//! rowan tree for source-ordered tokens and comments. Defaults mirror rustfmt: +//! a 100-column target, four-space indentation, LF line endings, no trailing +//! whitespace, at most one blank line, and exactly one final newline. +//! +//! Configuration files, range formatting, malformed-tree formatting, comment +//! reflow, CLI integration, and LSP integration are intentionally deferred. + +mod document; +mod format; +mod renderer; +mod token_stream; + +use std::sync::Arc; + +use rue_ast::{AstDocument, AstNode}; +use rue_diagnostic::{Diagnostic, Source, SourceKind}; +use rue_lexer::Lexer; +use rue_parser::{Parser, SyntaxKind, SyntaxNode}; +use thiserror::Error; + +use crate::{format::format_document, renderer::render, token_stream::TokenStream}; + +/// Deterministic formatter settings. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FormatOptions { + /// Preferred maximum line width. + pub max_width: usize, + /// Number of spaces in one indentation level. + pub indent_width: usize, +} + +impl Default for FormatOptions { + fn default() -> Self { + Self { + max_width: 100, + indent_width: 4, + } + } +} + +/// A failure that prevents a safe formatting result. +#[derive(Debug, Error)] +pub enum FormatError { + /// The parser reported malformed source. No replacement text is returned. + #[error("source contains syntax errors")] + Parse { + /// Original parser diagnostics. + diagnostics: Vec, + }, + /// The syntax tree contains a kind unknown to this formatter version. + #[error("unsupported syntax kind: {0}")] + UnsupportedSyntax(SyntaxKind), + /// Lossless CST tokens were not encountered in source order. + #[error("token order violation: token at {next_start} follows end offset {previous_end}")] + TokenOrder { + /// End offset of the previous token. + previous_end: usize, + /// Start offset of the next token. + next_start: usize, + }, + /// A formatter invariant failed. + #[error("formatter invariant failed: {0}")] + Internal(String), +} + +/// Format a complete Rue source file. +/// +/// Malformed input is rejected. A successful result has also been reparsed, +/// checked for syntax and comment equivalence, and formatted a second time to +/// prove idempotency. +pub fn format_source(source: &str, options: &FormatOptions) -> Result { + let input = parse(source)?; + let formatted = format_once(&input, options)?; + let output = parse(&formatted)?; + + verify_equivalence(&input, &output)?; + + let second = format_once(&output, options)?; + if second != formatted { + return Err(FormatError::Internal( + "formatting was not idempotent".to_string(), + )); + } + + Ok(formatted) +} + +fn format_once(parsed: &Parsed, options: &FormatOptions) -> Result { + let stream = TokenStream::from_syntax(parsed.document.syntax())?; + let doc = format_document(&parsed.document, &stream)?; + Ok(render(&doc, options)) +} + +#[derive(Debug)] +struct Parsed { + document: AstDocument, +} + +fn parse(text: &str) -> Result { + // Rue line-comment tokens include their terminating newline. Supplying one + // here also lets the current lexer represent a line comment at physical EOF. + let parse_text = if text.ends_with('\n') { + text.to_string() + } else { + format!("{text}\n") + }; + let source = Source::new( + Arc::from(parse_text.as_str()), + SourceKind::File("".to_string()), + ); + let result = Parser::new(source, Lexer::new(&parse_text).collect()).parse(); + if !result.diagnostics.is_empty() { + return Err(FormatError::Parse { + diagnostics: result.diagnostics, + }); + } + let document = AstDocument::cast(result.node) + .ok_or_else(|| FormatError::Internal("parser root was not an AstDocument".to_string()))?; + Ok(Parsed { document }) +} + +fn verify_equivalence(before: &Parsed, after: &Parsed) -> Result<(), FormatError> { + if structural_signature(before.document.syntax()) + != structural_signature(after.document.syntax()) + { + return Err(FormatError::Internal( + "formatted source changed syntax".to_string(), + )); + } + + let before_stream = TokenStream::from_syntax(before.document.syntax())?; + let after_stream = TokenStream::from_syntax(after.document.syntax())?; + let before_comments = comment_signature(&before_stream); + let after_comments = comment_signature(&after_stream); + if before_comments != after_comments { + return Err(FormatError::Internal( + "formatted source changed or duplicated comments".to_string(), + )); + } + Ok(()) +} + +fn structural_signature(root: &SyntaxNode) -> Vec { + let mut signature = Vec::new(); + for element in root.descendants_with_tokens() { + match element { + rowan::NodeOrToken::Node(node) => signature.push(format!("n:{:?}", node.kind())), + rowan::NodeOrToken::Token(token) if !token.kind().is_trivia() => { + signature.push(format!("t:{:?}:{}", token.kind(), token.text())); + } + rowan::NodeOrToken::Token(_) => {} + } + } + signature +} + +fn comment_signature(stream: &TokenStream) -> Vec<(SyntaxKind, &str)> { + stream + .gaps + .iter() + .flat_map(|gap| { + gap.comments + .iter() + .map(|comment| (comment.kind, comment.text.as_str())) + }) + .collect() +} + +#[cfg(test)] +mod tests; diff --git a/crates/rue-formatter/src/renderer.rs b/crates/rue-formatter/src/renderer.rs new file mode 100644 index 00000000..4eabfd9e --- /dev/null +++ b/crates/rue-formatter/src/renderer.rs @@ -0,0 +1,264 @@ +use crate::{ + FormatOptions, + document::{Doc, LineKind}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Mode { + Flat, + Broken, +} + +#[derive(Debug, Clone, Copy)] +struct Command<'a> { + indent: usize, + mode: Mode, + doc: &'a Doc, +} + +pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { + let mut output = String::new(); + let mut column = 0; + let mut commands = vec![Command { + indent: 0, + mode: Mode::Broken, + doc, + }]; + + while let Some(command) = commands.pop() { + match command.doc { + Doc::Nil => {} + Doc::Text(text) => { + output.push_str(text); + column += text.chars().count(); + } + Doc::Concat(docs) => { + for doc in docs.iter().rev() { + commands.push(Command { + indent: command.indent, + mode: command.mode, + doc, + }); + } + } + Doc::Line(LineKind::Soft) if command.mode == Mode::Flat => { + output.push(' '); + column += 1; + } + Doc::PreferredBreak { + doc, + indent_on_break, + } => { + if column < options.max_width + && fits_broken_prefix(options.max_width - column - 1, doc, &commands) + { + output.push(' '); + column += 1; + commands.push(Command { + indent: command.indent, + mode: command.mode, + doc, + }); + } else { + output.push('\n'); + let indent = command.indent + + if *indent_on_break { + options.indent_width + } else { + 0 + }; + output.extend(std::iter::repeat_n(' ', indent)); + column = indent; + commands.push(Command { + indent, + mode: command.mode, + doc, + }); + } + } + Doc::Line(kind) => { + output.push('\n'); + if *kind == LineKind::Empty && !output.ends_with("\n\n") { + output.push('\n'); + } + output.extend(std::iter::repeat_n(' ', command.indent)); + column = command.indent; + } + Doc::Indent(doc) => commands.push(Command { + indent: command.indent + options.indent_width, + mode: command.mode, + doc, + }), + Doc::Group(doc) => { + let mode = if has_forced_line(doc) + || !fits( + options.max_width.saturating_sub(column), + command.indent, + doc, + &commands, + ) { + Mode::Broken + } else { + Mode::Flat + }; + commands.push(Command { + indent: command.indent, + mode, + doc, + }); + } + Doc::IfBreak { broken, flat } => commands.push(Command { + indent: command.indent, + mode: command.mode, + doc: if command.mode == Mode::Broken { + broken + } else { + flat + }, + }), + } + } + + normalize_output(&output) +} + +fn has_forced_line(doc: &Doc) -> bool { + match doc { + Doc::Nil | Doc::Text(_) | Doc::Line(LineKind::Soft) => false, + Doc::Line(LineKind::Hard | LineKind::Empty) => true, + Doc::Concat(docs) => docs.iter().any(has_forced_line), + Doc::Indent(doc) | Doc::Group(doc) => has_forced_line(doc), + Doc::PreferredBreak { doc, .. } => has_forced_line(doc), + Doc::IfBreak { flat, .. } => has_forced_line(flat), + } +} + +fn fits( + mut remaining: usize, + initial_indent: usize, + doc: &Doc, + remaining_commands: &[Command<'_>], +) -> bool { + let mut commands = remaining_commands.to_vec(); + commands.push(Command { + indent: initial_indent, + mode: Mode::Flat, + doc, + }); + + while let Some(command) = commands.pop() { + match command.doc { + Doc::Nil => {} + Doc::Text(text) => { + let width = text.chars().count(); + if width > remaining { + return false; + } + remaining -= width; + } + Doc::Concat(docs) => { + for doc in docs.iter().rev() { + commands.push(Command { + indent: command.indent, + mode: command.mode, + doc, + }); + } + } + Doc::Line(LineKind::Soft) if command.mode == Mode::Flat => { + if remaining == 0 { + return false; + } + remaining -= 1; + } + Doc::Line(LineKind::Soft) => return true, + Doc::Line(LineKind::Hard | LineKind::Empty) => return true, + Doc::Indent(doc) | Doc::Group(doc) => commands.push(Command { + indent: command.indent, + mode: command.mode, + doc, + }), + Doc::PreferredBreak { doc, .. } => { + if remaining == 0 { + return false; + } + remaining -= 1; + commands.push(Command { + indent: command.indent, + mode: command.mode, + doc, + }); + } + Doc::IfBreak { broken, flat } => commands.push(Command { + indent: command.indent, + mode: command.mode, + doc: if command.mode == Mode::Broken { + broken + } else { + flat + }, + }), + } + } + + true +} + +fn fits_broken_prefix(mut remaining: usize, doc: &Doc, remaining_commands: &[Command<'_>]) -> bool { + let mut commands = remaining_commands.to_vec(); + commands.push(Command { + indent: 0, + mode: Mode::Broken, + doc, + }); + + while let Some(command) = commands.pop() { + match command.doc { + Doc::Nil => {} + Doc::Text(text) => { + let width = text.chars().count(); + if width > remaining { + return false; + } + remaining -= width; + } + Doc::Concat(docs) => { + for doc in docs.iter().rev() { + commands.push(Command { + indent: command.indent, + mode: Mode::Broken, + doc, + }); + } + } + Doc::Line(_) => return true, + Doc::Indent(doc) | Doc::Group(doc) => commands.push(Command { + indent: command.indent, + mode: Mode::Broken, + doc, + }), + Doc::PreferredBreak { .. } => return true, + Doc::IfBreak { broken, .. } => commands.push(Command { + indent: command.indent, + mode: Mode::Broken, + doc: broken, + }), + } + } + + true +} + +fn normalize_output(output: &str) -> String { + let mut normalized = output.to_string(); + + while normalized.ends_with("\n\n\n") { + normalized.pop(); + } + + if !normalized.ends_with('\n') { + normalized.push('\n'); + } + + normalized +} diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs new file mode 100644 index 00000000..a4843f0e --- /dev/null +++ b/crates/rue-formatter/src/tests.rs @@ -0,0 +1,263 @@ +use expect_test::{Expect, expect}; + +use crate::{FormatError, FormatOptions, format_source}; + +#[allow(clippy::needless_pass_by_value)] +fn check(input: &str, expected: Expect) { + let output = format_source(input, &FormatOptions::default()).expect("source should format"); + expected.assert_eq(&output); + assert_eq!( + format_source(&output, &FormatOptions::default()).unwrap(), + output + ); +} + +#[test] +fn items_blocks_and_expressions() { + check( + "inline const VALUE:Int=1+2*3;\nfn main( x:Int)->Int{let y=x+VALUE;y}", + expect![[r#" + inline const VALUE: Int = 1 + 2 * 3; + + fn main(x: Int) -> Int { + let y = x + VALUE; + y + } + "#]], + ); +} + +#[test] +fn types_bindings_generics_and_imports() { + check( + "import foo::*; import foo::{bar,baz,}; type Pair=(T,T,); fn take(...[a,{x:y}]:[T,...T])->fn(a:T)->T{}", + expect![[r#" + import foo::*; + + import foo::{bar, baz,}; + + type Pair = (T, T,); + + fn take(...[a, { x: y }]: [T, ...T]) -> fn(a: T) -> T {} + "#]], + ); +} + +#[test] +fn comments_are_preserved_once() { + check( + "// lead\nfn main(/* a */x:Int){// before\nlet y=x/* op */+1; // trailing\n// tail\ny}", + expect![[r#" + // lead + fn main(/* a */ x: Int) { + // before + let y = x /* op */ + 1; // trailing + // tail + y + } + "#]], + ); +} + +#[test] +fn comment_position_matrix() { + check( + "/* file */\nfn main(/* parameter */x:Int)->Int{let y=[/* dangling */];x/* left */+/* right */1}// eof", + expect![[r#" + /* file */ + fn main(/* parameter */ x: Int) -> Int { + let y = [ /* dangling */ ]; + x /* left */ + /* right */ 1 + } // eof + "#]], + ); +} + +#[test] +fn multiline_comment_text_is_exact() { + let output = format_source( + "fn main(){/* first \nsecond */1}", + &FormatOptions::default(), + ) + .unwrap(); + assert!(output.contains("/* first \nsecond */")); +} + +#[test] +fn preserves_one_intentional_blank_line() { + check( + "fn main(){let a=1;\n\n\nlet b=2;\na+b}", + expect![[r#" + fn main() { + let a = 1; + + let b = 2; + a + b + } + "#]], + ); +} + +#[test] +fn width_breaks_delimited_groups() { + let options = FormatOptions { + max_width: 30, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(first:VeryLongType,second:VeryLongType)->VeryLongType{first(second,second)}", + &options, + ) + .unwrap(); + expect![[r#" + fn main( + first: VeryLongType, + second: VeryLongType + ) -> VeryLongType { + first(second, second) + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn malformed_input_is_rejected() { + let error = format_source("fn main( {", &FormatOptions::default()).unwrap_err(); + assert!(matches!(error, FormatError::Parse { .. })); +} + +#[test] +fn all_expression_families() { + check( + "fn f(x:Int)->Int{debug fn(a:T):T=>a;assert x is Int;return if x>0{{x}.field as Int}else{const{x}};}", + expect![[r#" + fn f(x: Int) -> Int { + debug fn(a: T): T => a; + assert x is Int; + return if x > 0 { + { + x + }.field as Int + } else { + const { + x + } + }; + } + "#]], + ); +} + +#[test] +fn standalone_comment_inside_broken_group() { + check( + "fn main(){[// explain\n1]}", + expect![[r#" + fn main() { + [ + // explain + 1 + ] + } + "#]], + ); +} + +#[test] +fn short_binary_expressions_stay_flat() { + check( + "fn main(){let num=42;fn()=>num+num}", + expect![[r#" + fn main() { + let num = 42; + fn() => num + num + } + "#]], + ); +} + +#[test] +fn nested_delimiters_use_single_indent() { + let options = FormatOptions { + max_width: 40, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){assert tree_hash(fizz_buzz(1,15))==tree_hash([1,2,3,4,5,6]);}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + assert tree_hash( + fizz_buzz(1, 15) + ) == tree_hash([1, 2, 3, 4, 5, 6]); + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn binary_chain_uses_one_continuation_indent() { + let options = FormatOptions { + max_width: 24, + ..FormatOptions::default() + }; + let output = + format_source("fn main(){first_value+second_value+third_value}", &options).unwrap(); + expect![[r#" + fn main() { + first_value + + second_value + + third_value + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn binary_operators_inside_if_are_not_chain_members() { + check( + r#"fn main(){1+if char=="["{2}else if char=="]"{3}else{4}}"#, + expect![[r#" + fn main() { + 1 + + if char == "[" { + 2 + } else if char == "]" { + 3 + } else { + 4 + } + } + "#]], + ); +} + +#[test] +fn comparison_wraps_rhs_inside_logical_chain() { + let options = FormatOptions { + max_width: 35, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){let x=first_condition&&second_value==call(first,second,third);}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + let x = first_condition + && second_value == call( + first, + second, + third + ); + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} diff --git a/crates/rue-formatter/src/token_stream.rs b/crates/rue-formatter/src/token_stream.rs new file mode 100644 index 00000000..f1361c54 --- /dev/null +++ b/crates/rue-formatter/src/token_stream.rs @@ -0,0 +1,134 @@ +use rue_parser::{SyntaxKind, SyntaxNode}; + +use crate::FormatError; + +#[derive(Debug, Clone)] +pub(crate) struct Token { + pub(crate) kind: SyntaxKind, + pub(crate) text: String, + pub(crate) start: usize, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct Gap { + pub(crate) comments: Vec, + pub(crate) newlines: usize, +} + +#[derive(Debug, Clone)] +pub(crate) struct Comment { + pub(crate) text: String, + pub(crate) kind: SyntaxKind, + pub(crate) newlines_before: usize, + pub(crate) trailing: bool, + pub(crate) multiline: bool, +} + +#[derive(Debug, Clone)] +pub(crate) struct TokenStream { + pub(crate) tokens: Vec, + pub(crate) gaps: Vec, + pub(crate) comment_count: usize, +} + +impl TokenStream { + pub(crate) fn from_syntax(root: &SyntaxNode) -> Result { + let raw_tokens: Vec<_> = root + .descendants_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .collect(); + let mut tokens = Vec::new(); + let mut gaps = vec![Gap::default()]; + let mut pending_newlines = 0; + let mut line_has_significant = false; + let mut comment_count = 0; + let mut last_end = 0; + + for raw in raw_tokens { + let start: usize = raw.text_range().start().into(); + let end: usize = raw.text_range().end().into(); + if start < last_end { + return Err(FormatError::TokenOrder { + previous_end: last_end, + next_start: start, + }); + } + last_end = end; + + match raw.kind() { + SyntaxKind::Whitespace => { + let count = newline_count(raw.text()); + pending_newlines += count; + if count > 0 { + line_has_significant = false; + } + } + kind @ (SyntaxKind::LineComment | SyntaxKind::BlockComment) => { + let text = if kind == SyntaxKind::LineComment { + raw.text().trim_end_matches(['\r', '\n']).to_string() + } else { + raw.text().to_string() + }; + let multiline = + kind == SyntaxKind::BlockComment && newline_count(raw.text()) > 0; + gaps.last_mut() + .expect("a leading gap always exists") + .comments + .push(Comment { + text, + kind, + newlines_before: pending_newlines, + trailing: line_has_significant && pending_newlines == 0, + multiline, + }); + comment_count += 1; + pending_newlines = 0; + if kind == SyntaxKind::LineComment || multiline { + line_has_significant = false; + } + } + kind if kind.is_trivia() => { + return Err(FormatError::UnsupportedSyntax(kind)); + } + kind => { + gaps.last_mut() + .expect("a leading gap always exists") + .newlines = pending_newlines; + pending_newlines = 0; + tokens.push(Token { + kind, + text: raw.text().to_string(), + start, + }); + gaps.push(Gap::default()); + line_has_significant = true; + } + } + } + + gaps.last_mut() + .expect("a trailing gap always exists") + .newlines = pending_newlines; + if gaps.len() != tokens.len() + 1 { + return Err(FormatError::Internal( + "token stream did not produce one more gap than tokens".to_string(), + )); + } + + Ok(Self { + tokens, + gaps, + comment_count, + }) + } +} + +fn newline_count(text: &str) -> usize { + text.matches('\n').count() + + text + .as_bytes() + .windows(2) + .filter(|pair| pair[0] == b'\r' && pair[1] != b'\n') + .count() + + usize::from(text.ends_with('\r')) +} diff --git a/crates/rue-tests/Cargo.toml b/crates/rue-tests/Cargo.toml index 47c0b332..758fff7a 100644 --- a/crates/rue-tests/Cargo.toml +++ b/crates/rue-tests/Cargo.toml @@ -20,6 +20,7 @@ rue-compiler = { workspace = true } rue-options = { workspace = true } rue-diagnostic = { workspace = true } rue-lir = { workspace = true } +rue-formatter = { workspace = true } clvmr = { workspace = true } chialisp = { workspace = true } anyhow = { workspace = true } diff --git a/crates/rue-tests/src/main.rs b/crates/rue-tests/src/main.rs index a79d6b14..6b9f92c7 100644 --- a/crates/rue-tests/src/main.rs +++ b/crates/rue-tests/src/main.rs @@ -16,6 +16,7 @@ use indexmap::IndexMap; use rue_compiler::normalize_path; use rue_compiler::{Compiler, FileTree}; use rue_diagnostic::DiagnosticSeverity; +use rue_formatter::{FormatError, FormatOptions, format_source}; use rue_lir::DebugDialect; use rue_options::CompilerOptions; use serde::{Deserialize, Serialize}; @@ -91,6 +92,7 @@ fn main() -> Result<()> { fn run_tests(filter_arg: Option<&str>, base_path: &Path, update: bool) -> Result { let mut failed = false; + check_formatting_corpus(base_path, filter_arg, update, &mut failed)?; walk_dir(&base_path.join("tests"), filter_arg, update, &mut failed)?; walk_dir(&base_path.join("examples"), filter_arg, update, &mut failed)?; @@ -172,6 +174,67 @@ fn run_tests(filter_arg: Option<&str>, base_path: &Path, update: bool) -> Result Ok(failed) } +fn check_formatting_corpus( + base_path: &Path, + filter_arg: Option<&str>, + update: bool, + failed: &mut bool, +) -> Result<()> { + println!("Checking Rue formatting"); + let mut files = Vec::new(); + collect_rue_files(&base_path.join("tests"), &mut files)?; + collect_rue_files(&base_path.join("examples"), &mut files)?; + collect_rue_files(&base_path.join("crates/rue-compiler/src/std"), &mut files)?; + files.sort(); + + for path in files { + let display = path.strip_prefix(base_path).unwrap_or(&path); + if let Some(filter) = filter_arg + && !display.to_string_lossy().contains(filter) + { + continue; + } + + let original = fs::read_to_string(&path)?; + let formatted = match format_source(&original, &FormatOptions::default()) { + Ok(formatted) => formatted, + Err(FormatError::Parse { .. }) => continue, + Err(error) => { + eprintln!("Formatter failed for {}: {error}", display.display()); + *failed = true; + continue; + } + }; + + if formatted == original { + continue; + } + + *failed = true; + if update { + fs::write(&path, formatted)?; + eprintln!("Formatting differed, updated {}", display.display()); + } else { + eprintln!("Formatting differs for {}", display.display()); + } + } + + Ok(()) +} + +fn collect_rue_files(path: &Path, files: &mut Vec) -> Result<()> { + for entry in fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + collect_rue_files(&path, files)?; + } else if path.extension().is_some_and(|extension| extension == "rue") { + files.push(path); + } + } + Ok(()) +} + fn walk_dir(path: &Path, filter_arg: Option<&str>, update: bool, failed: &mut bool) -> Result<()> { let mut directories = IndexMap::new(); let mut directories_to_exclude = HashSet::new(); diff --git a/examples/brainfck.rue b/examples/brainfck.rue index f6aa7740..3db8dc97 100644 --- a/examples/brainfck.rue +++ b/examples/brainfck.rue @@ -30,44 +30,23 @@ fn step(program: Program) -> Program { let source = substr(source, 1); if char == "+" { - Program { - state: increment(state), - source, - } + Program { state: increment(state), source, } } else if char == "-" { - Program { - state: decrement(state), - source, - } + Program { state: decrement(state), source, } } else if char == "<" { - Program { - state: move_left(state), - source, - } + Program { state: move_left(state), source, } } else if char == ">" { - Program { - state: move_right(state), - source, - } + Program { state: move_right(state), source, } } else if char == "," { - Program { - state: input(state), - source, - } + Program { state: input(state), source, } } else if char == "." { - Program { - state: output(state), - source, - } + Program { state: output(state), source, } } else if char == "[" { let length = block_length(source, 1); let block = substr(source, 0, length); let source = substr(source, length); - Program { - state: loop(state, block), - source, - } + Program { state: loop(state, block), source, } } else { Program { state, source } } @@ -89,17 +68,18 @@ fn block_length(source: String, depth: Int) -> Int { let char = substr(source, 0, 1); let rest = substr(source, 1); - 1 + if char == "[" { - block_length(rest, depth + 1) - } else if char == "]" { - if depth == 1 { - 0 + 1 + + if char == "[" { + block_length(rest, depth + 1) + } else if char == "]" { + if depth == 1 { + 0 + } else { + block_length(rest, depth - 1) + } } else { - block_length(rest, depth - 1) + block_length(rest, depth) } - } else { - block_length(rest, depth) - } } inline fn update(state: State, tape: Tape) -> State { @@ -111,38 +91,25 @@ inline fn update(state: State, tape: Tape) -> State { inline fn increment(state: State) -> State { inline let { tape } = state; - update(state, Tape { - value: tape.value + 1, - left: tape.left, - right: tape.right, - }) + update(state, Tape { value: tape.value + 1, left: tape.left, right: tape.right, }) } inline fn decrement(state: State) -> State { inline let { tape } = state; - update(state, Tape { - value: tape.value - 1, - left: tape.left, - right: tape.right, - }) + update(state, Tape { value: tape.value - 1, left: tape.left, right: tape.right, }) } inline fn move_left(state: State) -> State { inline let { tape } = state; if tape.left is nil { - update(state, Tape { - value: 0, - left: nil, - right: (tape.value, tape.right), - }) + update(state, Tape { value: 0, left: nil, right: (tape.value, tape.right), }) } else { - update(state, Tape { - value: tape.left.first, - left: tape.left.rest, - right: (tape.value, tape.right), - }) + update( + state, + Tape { value: tape.left.first, left: tape.left.rest, right: (tape.value, tape.right), } + ) } } @@ -150,39 +117,26 @@ inline fn move_right(state: State) -> State { inline let { tape } = state; if tape.right is nil { - update(state, Tape { - value: 0, - left: (tape.value, tape.left), - right: nil, - }) + update(state, Tape { value: 0, left: (tape.value, tape.left), right: nil, }) } else { - update(state, Tape { - value: tape.right.first, - left: (tape.value, tape.left), - right: tape.right.rest, - }) + update( + state, + Tape { value: tape.right.first, left: (tape.value, tape.left), right: tape.right.rest, } + ) } } inline fn output(state: State) -> State { inline let { tape, input, output } = state; - State { - tape, - input, - output: output + tape.value as String, - } + State { tape, input, output: output + tape.value as String, } } inline fn input(state: State) -> State { inline let { tape, input, output } = state; State { - tape: Tape { - value: substr(input, 0, 1) as Int, - left: tape.left, - right: tape.right, - }, + tape: Tape { value: substr(input, 0, 1) as Int, left: tape.left, right: tape.right, }, input: substr(input, 1), output, } @@ -190,15 +144,7 @@ inline fn input(state: State) -> State { inline fn run(source: String, input: String) -> String { let program = Program { - state: State { - tape: Tape { - value: 0, - left: nil, - right: nil, - }, - input, - output: "", - }, + state: State { tape: Tape { value: 0, left: nil, right: nil, }, input, output: "", }, source, }; diff --git a/examples/brainfck.yaml b/examples/brainfck.yaml index 104047ba..a3605c87 100644 --- a/examples/brainfck.yaml +++ b/examples/brainfck.yaml @@ -1,7 +1,7 @@ tests: - name: abc program: (a (q 5 (r (r (f (a 12 (c (c 8 (c 12 14)) (c (q (() () ()) () ()) (c (concat (a 10 (c 10 (q 43 . 65))) (q . ".+.+.")) ())))))))) (c (c (c (q 2 (i 7 (q 2 (q 16 (a (i (= 6 (q . 91)) (q 2 5 (c 5 (c (+ 11 (q . 1)) 4))) (q 2 (i (= 6 (q . 93)) (q 2 (i (= 11 (q . 1)) (q) (q 2 5 (c 5 (c (- 11 (q . 1)) 4)))) 1) (q 2 5 (c 5 (c 11 4)))) 1)) 1) (q . 1)) (c (c (substr 7 (q . 1)) (substr 7 () (q . 1))) 1)) (q)) 1) (q 2 (i 11 (q 2 10 (c 2 (a 30 1))) (q . 3)) 1)) (c (q 2 (i (> 7 ()) (q 14 5 (a 2 (c 2 (c 5 (- 7 (q . 1)))))) (q)) 1) (c (q 2 (i 17 (q 2 22 (c 2 (c (f (a 10 (c 2 (c 5 (c 7 ()))))) 7))) (q . 5)) 1) (q 2 (q 2 (i (= 6 (q . 43)) (q 4 (c (c (+ 35 (q . 1)) (c 83 (c -77 ()))) (c 43 (c 91 ()))) (c 4 ())) (q 2 (i (= 6 (q . 45)) (q 4 (c (c (- 35 (q . 1)) (c 83 (c -77 ()))) (c 43 (c 91 ()))) (c 4 ())) (q 2 (i (= 6 (q . 60)) (q 4 (a (i 83 (q 4 (c -109 (c -45 (c (c 35 -77) ()))) (c 43 (c 91 ()))) (q 4 (c () (c () (c (c 35 -77) ()))) (c 43 (c 91 ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 62)) (q 4 (a (i -77 (q 4 (c 307 (c (c 35 83) (c 435 ()))) (c 43 (c 91 ()))) (q 4 (c () (c (c 35 83) (q ()))) (c 43 (c 91 ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 44)) (q 4 (c (c (substr 43 () (q . 1)) (c 83 (c -77 ()))) (c (substr 43 (q . 1)) (c 91 ()))) (c 4 ())) (q 2 (i (= 6 (q . 46)) (q 4 (c 19 (c 43 (c (concat 91 35) ()))) (c 4 ())) (q 2 (i (= 6 (q . 91)) (q 2 (q 4 (a 91 (c 11 (c 23 (substr 9 () 2)))) (c (substr 9 2) ())) (c (a 9 (c 9 (c (q . 1) 4))) 1)) (q 4 11 (c 4 ()))) 1)) 1)) 1)) 1)) 1)) 1)) 1) (c (c (substr 11 (q . 1)) (substr 11 () (q . 1))) 1))))) ())) - debug_program: (a (q 2 (q 2 (q 2 (i ("debug_print" (q . "brainfck.rue:207:5") (concat (concat (concat (concat (concat (concat (q . "run(`") (concat (a 43 (c 43 (c (q . 43) (q . 65)))) (q . ".+.+."))) (q . "`, `")) ()) (q . "`) = `")) 2) (q . 96))) (q) (q . 2)) 1) (c (f (r (r (f (a 25 (c (c 17 (c 25 (c 45 61))) 2)))))) 1)) (c (c (c (c () (c () (c () ()))) (c () (c () ()))) (c (concat (a 10 (c 10 (c (q . 43) (q . 65)))) (q . ".+.+.")) ())) 1)) (c (c (c (q 2 (i (= 7 ()) (q) (q 2 (q 16 (q . 1) (a (i (= 6 (q . 91)) (q 2 5 (c 5 (c (+ 11 (q . 1)) 4))) (q 2 (i (= 6 (q . 93)) (q 2 (i (= 11 (q . 1)) (q) (q 2 5 (c 5 (c (- 11 (q . 1)) 4)))) 1) (q 2 5 (c 5 (c 11 4)))) 1)) 1)) (c (c (substr 7 (q . 1)) (substr 7 () (q . 1))) 1))) 1) (q 2 (i (= (f (r 3)) ()) (q . 3) (q 2 10 (c (c 4 (c 10 (c 22 30))) (a 30 (c (c 4 (c 10 (c 22 30))) 3))))) 1)) (c (q 2 (i (> 7 ()) (q 14 5 (a 2 (c 2 (c 5 (- 7 (q . 1)))))) (q)) 1) (c (q 2 (i (= (f (f 5)) ()) (q . 5) (q 2 22 (c (c 4 (c 10 (c 22 30))) (c (f (a 10 (c (c 4 (c 10 (c 22 30))) (c 5 (c 7 ()))))) 7)))) 1) (q 2 (q 2 (i (= 6 (q . 43)) (q 4 (c (c (+ (f (f (f 7))) (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 45)) (q 4 (c (c (- (f (f (f 7))) (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 60)) (q 4 (a (i (not (l (f (r (f (f 7)))))) (q 4 (c () (c () (c (c (f (f (f 7))) (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (q 4 (c (f (f (r (f (f 7))))) (c (r (f (r (f (f 7))))) (c (c (f (f (f 7))) (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 62)) (q 4 (a (i (not (l (f (r (r (f (f 7))))))) (q 4 (c () (c (c (f (f (f 7))) (f (r (f (f 7))))) (c () ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (q 4 (c (f (f (r (r (f (f 7)))))) (c (c (f (f (f 7))) (f (r (f (f 7))))) (c (r (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 44)) (q 4 (c (c (substr (f (r (f 7))) () (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (substr (f (r (f 7))) (q . 1)) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 46)) (q 4 (c (f (f 7)) (c (f (r (f 7))) (c (concat (f (r (r (f 7)))) (f (f (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 91)) (q 2 (q 2 (q 4 (a -73 (c (c 39 (c 87 (c -73 -9))) (c (f 31) 4))) (c 6 ())) (c (c (substr 9 () 2) (substr 9 2)) 1)) (c (a 9 (c 9 (c (q . 1) 4))) 1)) (q 4 (f 7) (c 4 ()))) 1)) 1)) 1)) 1)) 1)) 1)) 1) (c (c (substr (f (r 3)) (q . 1)) (substr (f (r 3)) () (q . 1))) 1))))) ())) + debug_program: (a (q 2 (q 2 (q 2 (i ("debug_print" (q . "brainfck.rue:153:5") (concat (concat (concat (concat (concat (concat (q . "run(`") (concat (a 43 (c 43 (c (q . 43) (q . 65)))) (q . ".+.+."))) (q . "`, `")) ()) (q . "`) = `")) 2) (q . 96))) (q) (q . 2)) 1) (c (f (r (r (f (a 25 (c (c 17 (c 25 (c 45 61))) 2)))))) 1)) (c (c (c (c () (c () (c () ()))) (c () (c () ()))) (c (concat (a 10 (c 10 (c (q . 43) (q . 65)))) (q . ".+.+.")) ())) 1)) (c (c (c (q 2 (i (= 7 ()) (q) (q 2 (q 16 (q . 1) (a (i (= 6 (q . 91)) (q 2 5 (c 5 (c (+ 11 (q . 1)) 4))) (q 2 (i (= 6 (q . 93)) (q 2 (i (= 11 (q . 1)) (q) (q 2 5 (c 5 (c (- 11 (q . 1)) 4)))) 1) (q 2 5 (c 5 (c 11 4)))) 1)) 1)) (c (c (substr 7 (q . 1)) (substr 7 () (q . 1))) 1))) 1) (q 2 (i (= (f (r 3)) ()) (q . 3) (q 2 10 (c (c 4 (c 10 (c 22 30))) (a 30 (c (c 4 (c 10 (c 22 30))) 3))))) 1)) (c (q 2 (i (> 7 ()) (q 14 5 (a 2 (c 2 (c 5 (- 7 (q . 1)))))) (q)) 1) (c (q 2 (i (= (f (f 5)) ()) (q . 5) (q 2 22 (c (c 4 (c 10 (c 22 30))) (c (f (a 10 (c (c 4 (c 10 (c 22 30))) (c 5 (c 7 ()))))) 7)))) 1) (q 2 (q 2 (i (= 6 (q . 43)) (q 4 (c (c (+ (f (f (f 7))) (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 45)) (q 4 (c (c (- (f (f (f 7))) (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 60)) (q 4 (a (i (not (l (f (r (f (f 7)))))) (q 4 (c () (c () (c (c (f (f (f 7))) (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (q 4 (c (f (f (r (f (f 7))))) (c (r (f (r (f (f 7))))) (c (c (f (f (f 7))) (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 62)) (q 4 (a (i (not (l (f (r (r (f (f 7))))))) (q 4 (c () (c (c (f (f (f 7))) (f (r (f (f 7))))) (c () ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (q 4 (c (f (f (r (r (f (f 7)))))) (c (c (f (f (f 7))) (f (r (f (f 7))))) (c (r (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 44)) (q 4 (c (c (substr (f (r (f 7))) () (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (substr (f (r (f 7))) (q . 1)) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 46)) (q 4 (c (f (f 7)) (c (f (r (f 7))) (c (concat (f (r (r (f 7)))) (f (f (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 91)) (q 2 (q 2 (q 4 (a -73 (c (c 39 (c 87 (c -73 -9))) (c (f 31) 4))) (c 6 ())) (c (c (substr 9 () 2) (substr 9 2)) 1)) (c (a 9 (c 9 (c (q . 1) 4))) 1)) (q 4 (f 7) (c 4 ()))) 1)) 1)) 1)) 1)) 1)) 1)) 1) (c (c (substr (f (r 3)) (q . 1)) (substr (f (r 3)) () (q . 1))) 1))))) ())) output: '"ABC"' runtime_cost: 423451 byte_cost: 15288000 @@ -10,7 +10,7 @@ tests: - '"run(`+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.+.+.`, ``) = `ABC`"' - name: alphabet program: (a (q 5 (r (r (f (a 10 (c (c 8 6) (c (q (() () ()) () ()) (c (concat (q . 62) (a 12 (c 12 (q 43 . 65))) (q . 60) (a 12 (c 12 (q 43 . 26))) (q . "[->.+<]")) ())))))))) (c (c (c (q 2 (i 7 (q 2 (q 16 (a (i (= 6 (q . 91)) (q 2 5 (c 5 (c (+ 11 (q . 1)) 4))) (q 2 (i (= 6 (q . 93)) (q 2 (i (= 11 (q . 1)) (q) (q 2 5 (c 5 (c (- 11 (q . 1)) 4)))) 1) (q 2 5 (c 5 (c 11 4)))) 1)) 1) (q . 1)) (c (c (substr 7 (q . 1)) (substr 7 () (q . 1))) 1)) (q)) 1) (q 2 (i (> 7 ()) (q 14 5 (a 2 (c 2 (c 5 (- 7 (q . 1)))))) (q)) 1)) (c (q 2 (i 11 (q 2 10 (c 2 (a 30 1))) (q . 3)) 1) (c (q 2 (i 17 (q 2 22 (c 2 (c (f (a 10 (c 2 (c 5 (c 7 ()))))) 7))) (q . 5)) 1) (q 2 (q 2 (i (= 6 (q . 43)) (q 4 (c (c (+ 35 (q . 1)) (c 83 (c -77 ()))) (c 43 (c 91 ()))) (c 4 ())) (q 2 (i (= 6 (q . 45)) (q 4 (c (c (- 35 (q . 1)) (c 83 (c -77 ()))) (c 43 (c 91 ()))) (c 4 ())) (q 2 (i (= 6 (q . 60)) (q 4 (a (i 83 (q 4 (c -109 (c -45 (c (c 35 -77) ()))) (c 43 (c 91 ()))) (q 4 (c () (c () (c (c 35 -77) ()))) (c 43 (c 91 ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 62)) (q 4 (a (i -77 (q 4 (c 307 (c (c 35 83) (c 435 ()))) (c 43 (c 91 ()))) (q 4 (c () (c (c 35 83) (q ()))) (c 43 (c 91 ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 44)) (q 4 (c (c (substr 43 () (q . 1)) (c 83 (c -77 ()))) (c (substr 43 (q . 1)) (c 91 ()))) (c 4 ())) (q 2 (i (= 6 (q . 46)) (q 4 (c 19 (c 43 (c (concat 91 35) ()))) (c 4 ())) (q 2 (i (= 6 (q . 91)) (q 2 (q 4 (a 91 (c 11 (c 23 (substr 9 () 2)))) (c (substr 9 2) ())) (c (a 9 (c 9 (c (q . 1) 4))) 1)) (q 4 11 (c 4 ()))) 1)) 1)) 1)) 1)) 1)) 1)) 1) (c (c (substr 11 (q . 1)) (substr 11 () (q . 1))) 1))))) ())) - debug_program: (a (q 2 (q 2 (q 2 (i ("debug_print" (q . "brainfck.rue:207:5") (concat (concat (concat (concat (concat (concat (q . "run(`") (concat (concat (concat (concat (q . 62) (a 51 (c 51 (c (q . 43) (q . 65))))) (q . 60)) (a 51 (c 51 (c (q . 43) (q . 26))))) (q . "[->.+<]"))) (q . "`, `")) ()) (q . "`) = `")) 2) (q . 96))) (q) (q . 2)) 1) (c (f (r (r (f (a 21 (c (c 17 (c 21 (c 45 61))) 2)))))) 1)) (c (c (c (c () (c () (c () ()))) (c () (c () ()))) (c (concat (concat (concat (concat (q . 62) (a 12 (c 12 (c (q . 43) (q . 65))))) (q . 60)) (a 12 (c 12 (c (q . 43) (q . 26))))) (q . "[->.+<]")) ())) 1)) (c (c (c (q 2 (i (= 7 ()) (q) (q 2 (q 16 (q . 1) (a (i (= 6 (q . 91)) (q 2 5 (c 5 (c (+ 11 (q . 1)) 4))) (q 2 (i (= 6 (q . 93)) (q 2 (i (= 11 (q . 1)) (q) (q 2 5 (c 5 (c (- 11 (q . 1)) 4)))) 1) (q 2 5 (c 5 (c 11 4)))) 1)) 1)) (c (c (substr 7 (q . 1)) (substr 7 () (q . 1))) 1))) 1) (q 2 (i (> 7 ()) (q 14 5 (a 2 (c 2 (c 5 (- 7 (q . 1)))))) (q)) 1)) (c (q 2 (i (= (f (r 3)) ()) (q . 3) (q 2 10 (c (c 4 (c 10 (c 22 30))) (a 30 (c (c 4 (c 10 (c 22 30))) 3))))) 1) (c (q 2 (i (= (f (f 5)) ()) (q . 5) (q 2 22 (c (c 4 (c 10 (c 22 30))) (c (f (a 10 (c (c 4 (c 10 (c 22 30))) (c 5 (c 7 ()))))) 7)))) 1) (q 2 (q 2 (i (= 6 (q . 43)) (q 4 (c (c (+ (f (f (f 7))) (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 45)) (q 4 (c (c (- (f (f (f 7))) (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 60)) (q 4 (a (i (not (l (f (r (f (f 7)))))) (q 4 (c () (c () (c (c (f (f (f 7))) (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (q 4 (c (f (f (r (f (f 7))))) (c (r (f (r (f (f 7))))) (c (c (f (f (f 7))) (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 62)) (q 4 (a (i (not (l (f (r (r (f (f 7))))))) (q 4 (c () (c (c (f (f (f 7))) (f (r (f (f 7))))) (c () ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (q 4 (c (f (f (r (r (f (f 7)))))) (c (c (f (f (f 7))) (f (r (f (f 7))))) (c (r (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 44)) (q 4 (c (c (substr (f (r (f 7))) () (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (substr (f (r (f 7))) (q . 1)) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 46)) (q 4 (c (f (f 7)) (c (f (r (f 7))) (c (concat (f (r (r (f 7)))) (f (f (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 91)) (q 2 (q 2 (q 4 (a -73 (c (c 39 (c 87 (c -73 -9))) (c (f 31) 4))) (c 6 ())) (c (c (substr 9 () 2) (substr 9 2)) 1)) (c (a 9 (c 9 (c (q . 1) 4))) 1)) (q 4 (f 7) (c 4 ()))) 1)) 1)) 1)) 1)) 1)) 1)) 1) (c (c (substr (f (r 3)) (q . 1)) (substr (f (r 3)) () (q . 1))) 1))))) ())) + debug_program: (a (q 2 (q 2 (q 2 (i ("debug_print" (q . "brainfck.rue:153:5") (concat (concat (concat (concat (concat (concat (q . "run(`") (concat (concat (concat (concat (q . 62) (a 51 (c 51 (c (q . 43) (q . 65))))) (q . 60)) (a 51 (c 51 (c (q . 43) (q . 26))))) (q . "[->.+<]"))) (q . "`, `")) ()) (q . "`) = `")) 2) (q . 96))) (q) (q . 2)) 1) (c (f (r (r (f (a 21 (c (c 17 (c 21 (c 45 61))) 2)))))) 1)) (c (c (c (c () (c () (c () ()))) (c () (c () ()))) (c (concat (concat (concat (concat (q . 62) (a 12 (c 12 (c (q . 43) (q . 65))))) (q . 60)) (a 12 (c 12 (c (q . 43) (q . 26))))) (q . "[->.+<]")) ())) 1)) (c (c (c (q 2 (i (= 7 ()) (q) (q 2 (q 16 (q . 1) (a (i (= 6 (q . 91)) (q 2 5 (c 5 (c (+ 11 (q . 1)) 4))) (q 2 (i (= 6 (q . 93)) (q 2 (i (= 11 (q . 1)) (q) (q 2 5 (c 5 (c (- 11 (q . 1)) 4)))) 1) (q 2 5 (c 5 (c 11 4)))) 1)) 1)) (c (c (substr 7 (q . 1)) (substr 7 () (q . 1))) 1))) 1) (q 2 (i (> 7 ()) (q 14 5 (a 2 (c 2 (c 5 (- 7 (q . 1)))))) (q)) 1)) (c (q 2 (i (= (f (r 3)) ()) (q . 3) (q 2 10 (c (c 4 (c 10 (c 22 30))) (a 30 (c (c 4 (c 10 (c 22 30))) 3))))) 1) (c (q 2 (i (= (f (f 5)) ()) (q . 5) (q 2 22 (c (c 4 (c 10 (c 22 30))) (c (f (a 10 (c (c 4 (c 10 (c 22 30))) (c 5 (c 7 ()))))) 7)))) 1) (q 2 (q 2 (i (= 6 (q . 43)) (q 4 (c (c (+ (f (f (f 7))) (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 45)) (q 4 (c (c (- (f (f (f 7))) (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 60)) (q 4 (a (i (not (l (f (r (f (f 7)))))) (q 4 (c () (c () (c (c (f (f (f 7))) (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (q 4 (c (f (f (r (f (f 7))))) (c (r (f (r (f (f 7))))) (c (c (f (f (f 7))) (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 62)) (q 4 (a (i (not (l (f (r (r (f (f 7))))))) (q 4 (c () (c (c (f (f (f 7))) (f (r (f (f 7))))) (c () ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ()))) (q 4 (c (f (f (r (r (f (f 7)))))) (c (c (f (f (f 7))) (f (r (f (f 7))))) (c (r (f (r (r (f (f 7)))))) ()))) (c (f (r (f 7))) (c (f (r (r (f 7)))) ())))) 1) (c 4 ())) (q 2 (i (= 6 (q . 44)) (q 4 (c (c (substr (f (r (f 7))) () (q . 1)) (c (f (r (f (f 7)))) (c (f (r (r (f (f 7))))) ()))) (c (substr (f (r (f 7))) (q . 1)) (c (f (r (r (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 46)) (q 4 (c (f (f 7)) (c (f (r (f 7))) (c (concat (f (r (r (f 7)))) (f (f (f 7)))) ()))) (c 4 ())) (q 2 (i (= 6 (q . 91)) (q 2 (q 2 (q 4 (a -73 (c (c 39 (c 87 (c -73 -9))) (c (f 31) 4))) (c 6 ())) (c (c (substr 9 () 2) (substr 9 2)) 1)) (c (a 9 (c 9 (c (q . 1) 4))) 1)) (q 4 (f 7) (c 4 ()))) 1)) 1)) 1)) 1)) 1)) 1)) 1) (c (c (substr (f (r 3)) (q . 1)) (substr (f (r 3)) () (q . 1))) 1))))) ())) output: '"ABCDEFGHIJKLMNOPQRSTUVWXYZ"' runtime_cost: 1243355 byte_cost: 15552000 diff --git a/examples/fizz_buzz.rue b/examples/fizz_buzz.rue index 71556274..a38b5579 100644 --- a/examples/fizz_buzz.rue +++ b/examples/fizz_buzz.rue @@ -40,9 +40,23 @@ fn single_digit_to_string(digit: Int) -> String { test fn tests() { assert tree_hash(fizz_buzz(1, 1)) == tree_hash(["1"]); assert tree_hash(fizz_buzz(1, 5)) == tree_hash(["1", "2", "Fizz", "4", "Buzz"]); - assert tree_hash(fizz_buzz(1, 15)) == tree_hash([ - "1", "2", "Fizz", "4", "Buzz", - "Fizz", "7", "8", "Fizz", "Buzz", - "11", "Fizz", "13", "14", "FizzBuzz", - ]); + assert tree_hash(fizz_buzz(1, 15)) == tree_hash( + [ + "1", + "2", + "Fizz", + "4", + "Buzz", + "Fizz", + "7", + "8", + "Fizz", + "Buzz", + "11", + "Fizz", + "13", + "14", + "FizzBuzz", + ] + ); } diff --git a/examples/puzzles/cat.rue b/examples/puzzles/cat.rue index 9b5b645e..b94c9c02 100644 --- a/examples/puzzles/cat.rue +++ b/examples/puzzles/cat.rue @@ -1,5 +1,4 @@ // This puzzle has not been audited or tested, and is for example purposes only. - // Information about the CAT, used for currying purposes. struct CatInfo { mod_hash: Bytes32, @@ -100,16 +99,12 @@ fn main( extra_delta: Int, ) -> List { // For simplicity, we'll pack these values into a struct. - let cat_info = CatInfo { - mod_hash, - mod_hash_hash: tree_hash_atom(mod_hash), - asset_id, - }; + let cat_info = CatInfo { mod_hash, mod_hash_hash: tree_hash_atom(mod_hash), asset_id, }; // Calculate the inner puzzle hash and conditions. let morph = morph_conditions(inner_puzzle(...inner_solution), cat_info, nil); let inner_puzzle_hash = tree_hash(inner_puzzle); - + // Calculate coin ids. let my_coin_id = coinid(my_coin.parent_coin_info, my_coin.puzzle_hash, my_coin.amount); let next_coin_id = coinid( @@ -125,11 +120,12 @@ fn main( }; // Check whether the parent is a CAT or not. - let parent_is_cat = lineage_proof is LineageProof && my_coin.parent_coin_info == coinid( - lineage_proof.parent_parent_coin_info, - cat_puzzle_hash(cat_info, lineage_proof.parent_inner_puzzle_hash), - lineage_proof.parent_amount, - ); + let parent_is_cat = lineage_proof is LineageProof + && my_coin.parent_coin_info == coinid( + lineage_proof.parent_parent_coin_info, + cat_puzzle_hash(cat_info, lineage_proof.parent_inner_puzzle_hash), + lineage_proof.parent_amount, + ); // Calculate the new subtotal. let remainder = my_coin.amount - morph.sum; @@ -202,26 +198,19 @@ fn morph_conditions( ) -> Morph { // If there are no conditions, return an empty morph. if conditions is nil { - return Morph { - conditions: nil, - sum: 0, - tail_info: tail_info, - }; + return Morph { conditions: nil, sum: 0, tail_info: tail_info, }; } inline let condition = conditions.first; if condition is RunTail { - let rest = morph_conditions(conditions.rest, cat_info, TailInfo { - tail_puzzle: condition.tail_puzzle, - tail_solution: condition.tail_solution, - }); - - return Morph { - conditions: rest.conditions, - sum: rest.sum, - tail_info: rest.tail_info, - }; + let rest = morph_conditions( + conditions.rest, + cat_info, + TailInfo { tail_puzzle: condition.tail_puzzle, tail_solution: condition.tail_solution, } + ); + + return Morph { conditions: rest.conditions, sum: rest.sum, tail_info: rest.tail_info, }; } let morphed = if condition is CreateCoin { @@ -237,8 +226,10 @@ fn morph_conditions( // If the condition is a coin announcement, // make sure it's not pretending to be part of the ring. if condition is CreateCoinAnnouncement - && ((condition.message.length == 33) & (substr(condition.message, 0, 1) == RING_MORPH_BYTE)) - { + && ( + (condition.message.length == 33) + & (substr(condition.message, 0, 1) == RING_MORPH_BYTE) + ) { raise "Created coin announcements must not have the ring morph byte"; } diff --git a/examples/puzzles/cat.yaml b/examples/puzzles/cat.yaml index 448f6241..964085c9 100644 --- a/examples/puzzles/cat.yaml +++ b/examples/puzzles/cat.yaml @@ -1,5 +1,5 @@ program: (a (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 27 ())) (a (i 57 (q 2 (i (= (a 39 (c 39 89)) 95) (q 2 -9 (c -9 (c (a 89 (c (c (c (a 39 (c 39 -65)) 19) (c 27 3071)) (c 13 (c 767 (c 24575 (c 2 (c -71 ()))))))) 2))) (q 8)) 1) (q 2 (i (all 13 (not 24575)) (q . 2) (q 8)) 1)) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a 19 (c 19 (c 767 (c 6143 ()))))) ())) (c (c (q . 61) (c (sha256 (coinid 5119 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 17) (sha256 (q . 2) (a 91 (c 91 (c 41 (c (sha256 (q . 1) 89) (c 11263 ()))))) (sha256 (q . 1))))) 23551) (q . -53) (a 19 (c 19 (c 13 (c (+ 6143 (- 11775 20) 12287) ()))))) ())) 8)) 1)) (c (c (a 21 (c (c 21 45) (c (a 47 95) (c 4 ())))) (a (i -65 (q 2 (i (= 1279 (coinid 319 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 8) (sha256 (q . 2) (a 45 (c 45 (c 20 (c (sha256 (q . 1) 44) (c 703 ()))))) (sha256 (q . 1))))) 1471)) (q 1 . 1) (q)) 1) (q)) 1)) 1)) (c (c (c 5 (c (sha256 (q . 1) 5) (c 11 ()))) (coinid 639 1407 2943)) 1)) (c (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (c (q 2 (i 5 (q 2 (i (a (i (= 17 (q . 51)) (q 2 (i 41 (q) (q 1 . 1)) 1) (q)) 1) (q 2 (q . 2) (c (a 4 (c 2 (c 13 (c 11 (c -71 (c 377 ())))))) 1)) (q 2 (q 4 (c 10 8) (c (+ 20 14) 28)) (c (c (a 4 (c 2 (c 13 7))) (a (i (= 17 (q . 51)) (q 4 (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 19) (sha256 (q . 2) (a 6 (c 6 (c 43 (c (sha256 (q . 1) 91) (c 41 ()))))) (sha256 (q . 1))))) 57)) 89) (q 2 (i (a (i (= 17 (q . 60)) (q 2 (i (all (= (strlen 41) (q . 33)) (= (substr 41 () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8) (q 4 9 ())) 1)) 1)) 1))) 1) (q 4 () (c () 15))) 1) (c (q 2 (i 3 (q 11 (q . 2) (sha256 (q . 260)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 5) (sha256 (q . 2) (a 2 (c 2 7)) (sha256 (q . 1))))) (q 11 (q . 257))) 1) (q 2 (i 5 (q 4 9 (a 2 (c 2 (c 13 7)))) (q . 7)) 1)))) 1)) -debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 351 ())) 2) (c (a (i (not (not (l (r (r 39))))) (q 2 (i (= (a 287 (c 287 (f (r (r 39))))) 383) (q 2 (q 2 1727 (c 1727 (c 2 5))) (c (a (f (r (r 39))) (c -9 (c 87 (c 3071 (c 0x017fff (c 2 (c (f (r (r (r 39)))) ()))))))) 1)) (q 8 (q . "assertion failed at cat.rue:153:9"))) 1) (q 2 (i (all 87 (= 0x017fff ())) (q . 2) (q 8 (q . "assertion failed at cat.rue:171:9"))) 1)) 1) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a -113 (c -113 (c 3071 (c 24575 ()))))) ())) (c (c (q . 61) (c (sha256 (concat (concat 91 (q . -53)) (a -113 (c -113 (c 87 (c 2 ())))))) ())) (f 19))) 1)) (c (+ (+ 12287 2) 24575) 1)) (c (- (f (r (r 1535))) (f (r 4))) 1)) (c (c (a 25 (c (c 25 (c 37 (c 45 (c 93 125)))) (c (a 47 95) (c 4 ())))) (c (a (i (l -65) (q 2 (i (= (f 767) (coinid (f -65) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r -65)) ())))))) (f (r (r -65))))) (q 1 . 1) (q)) 1) (q)) 1) (c (coinid (f 1535) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r 1535)) ())))))) (f (r (r 1535)))) (c (c 14 4) (c 10 767))))) 1)) (c (c (c 5 (c (sha256 (concat (q . 1) 5)) (c 11 ()))) (c (coinid (f 383) (f (r 383)) (f (r (r 383)))) (a 8 (c 8 23)))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 5)) (q 4 () (c () 15)) (q 2 (i (a (i (= (f (f 5)) (q . 51)) (q 2 (i (= (f (r (f 5))) ()) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (q 4 (f 2) (c (f (r 2)) (r (r 2)))) (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 (c (f (r (r (r (f 5))))) (c (f (r (r (r (r (f 5)))))) ())))))) 1)) (q 2 (q 4 (c (f 6) (f 4)) (c (+ (f (r 4)) (r 6)) (r (r 4)))) (c (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 15)))) (a (i (= (f (f 5)) (q . 51)) (q 2 (q 4 2 (f (r (r (f 11))))) (c (c (q . 51) (c (a 22 (c (c 10 (c 46 62)) (c (f 11) (c (f (r 11)) (c (sha256 (concat (q . 1) (f (r (r 11))))) (c (f (r (f 5))) ())))))) (c (f (r (r (f 5)))) (r (r (r (f 5))))))) 1)) (q 2 (i (a (i (= (f (f 5)) (q . 60)) (q 2 (i (all (= (strlen (f (r (f 5)))) (q . 33)) (= (substr (f (r (f 5))) () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8 (q . "raise called at cat.rue:242:13") (q . "Created coin announcements must not have the ring morph byte")) (q 4 (f 5) ())) 1)) 1)) 1))) 1)) 1)) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (q 2 (i (l 5) (q 4 (f 5) (a 2 (c 2 (c (r 5) 7)))) (q . 7)) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) +debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 351 ())) 2) (c (a (i (not (not (l (r (r 39))))) (q 2 (i (= (a 287 (c 287 (f (r (r 39))))) 383) (q 2 (q 2 1727 (c 1727 (c 2 5))) (c (a (f (r (r 39))) (c -9 (c 87 (c 3071 (c 0x017fff (c 2 (c (f (r (r (r 39)))) ()))))))) 1)) (q 8 (q . "assertion failed at cat.rue:149:9"))) 1) (q 2 (i (all 87 (= 0x017fff ())) (q . 2) (q 8 (q . "assertion failed at cat.rue:167:9"))) 1)) 1) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a -113 (c -113 (c 3071 (c 24575 ()))))) ())) (c (c (q . 61) (c (sha256 (concat (concat 91 (q . -53)) (a -113 (c -113 (c 87 (c 2 ())))))) ())) (f 19))) 1)) (c (+ (+ 12287 2) 24575) 1)) (c (- (f (r (r 1535))) (f (r 4))) 1)) (c (c (a 25 (c (c 25 (c 37 (c 45 (c 93 125)))) (c (a 47 95) (c 4 ())))) (c (a (i (l -65) (q 2 (i (= (f 767) (coinid (f -65) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r -65)) ())))))) (f (r (r -65))))) (q 1 . 1) (q)) 1) (q)) 1) (c (coinid (f 1535) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r 1535)) ())))))) (f (r (r 1535)))) (c (c 14 4) (c 10 767))))) 1)) (c (c (c 5 (c (sha256 (concat (q . 1) 5)) (c 11 ()))) (c (coinid (f 383) (f (r 383)) (f (r (r 383)))) (a 8 (c 8 23)))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 5)) (q 4 () (c () 15)) (q 2 (i (a (i (= (f (f 5)) (q . 51)) (q 2 (i (= (f (r (f 5))) ()) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (q 4 (f 2) (c (f (r 2)) (r (r 2)))) (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 (c (f (r (r (r (f 5))))) (c (f (r (r (r (r (f 5)))))) ())))))) 1)) (q 2 (q 4 (c (f 6) (f 4)) (c (+ (f (r 4)) (r 6)) (r (r 4)))) (c (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 15)))) (a (i (= (f (f 5)) (q . 51)) (q 2 (q 4 2 (f (r (r (f 11))))) (c (c (q . 51) (c (a 22 (c (c 10 (c 46 62)) (c (f 11) (c (f (r 11)) (c (sha256 (concat (q . 1) (f (r (r 11))))) (c (f (r (f 5))) ())))))) (c (f (r (r (f 5)))) (r (r (r (f 5))))))) 1)) (q 2 (i (a (i (= (f (f 5)) (q . 60)) (q 2 (i (all (= (strlen (f (r (f 5)))) (q . 33)) (= (substr (f (r (f 5))) () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8 (q . "raise called at cat.rue:233:13") (q . "Created coin announcements must not have the ring morph byte")) (q 4 (f 5) ())) 1)) 1)) 1))) 1)) 1)) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (q 2 (i (l 5) (q 4 (f 5) (a 2 (c 2 (c (r 5) 7)))) (q . 7)) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) solution: (0x00f43ce9fcc63d5019e209c103e6b0aaf56bbe7fc7fafae5af7f5ee6887a8719 0xd622c62a7292ffee5cf2537a90360ca0b7337b76d7014ec042930c0a87592213 (q (g1_negate () -113 (a (q 2 (i 47 (q 8) (q 2 (i (= 45 2) () (q 8)) 1)) 1) (c (q . 0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861) 1)) ()) (g1_negate 0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001 1 (0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001))) () () 0x615236766bed52d7abaa41d270407f3ec852981852334b213bd8515924459a5d (0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861 0x1ecb863db5d2ae6c71e9a8b0741acb3e034e8164b8ca0e564d5fad8b9dc875d5 1) (0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861 0x130deb20b44082a68293974f8cab9c51e21f9a9f3005000168eb77e49e0fc378 1) () ()) output: ((70 0x615236766bed52d7abaa41d270407f3ec852981852334b213bd8515924459a5d) (modpow 0xcb7f53b5de05b4afe58ab663b952aa785fd9ad911564709bd8eacbf4c58ba1e589) (% 0x389f1ea7b9fab7a9294104eb3a181d662f2dbe9c43fbe52802ba9b7eef357e77) (g1_negate 0xc9644528436f44cd9b33282684b4964f55d5551cb3a970ab9cf8f536e0d72ad1 1 (0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001))) runtime_cost: 297861 diff --git a/examples/puzzles/p2_delegated_conditions.rue b/examples/puzzles/p2_delegated_conditions.rue index f3c96e76..d47f2357 100644 --- a/examples/puzzles/p2_delegated_conditions.rue +++ b/examples/puzzles/p2_delegated_conditions.rue @@ -1,7 +1,4 @@ fn main(public_key: PublicKey, conditions: List) -> List { - let agg_sig = AggSigMe { - public_key, - message: tree_hash(conditions), - }; + let agg_sig = AggSigMe { public_key, message: tree_hash(conditions), }; [agg_sig, ...conditions] } diff --git a/examples/puzzles/partial_offer.rue b/examples/puzzles/partial_offer.rue index 6454ed17..c9799aa5 100644 --- a/examples/puzzles/partial_offer.rue +++ b/examples/puzzles/partial_offer.rue @@ -1,5 +1,4 @@ // This puzzle has not been audited or tested, and is for example purposes only. - struct Precisions { price_precision: Int, ...precision: Int, @@ -33,18 +32,23 @@ fn main( ...inner_puzzle_solution: Any, ) -> List { let assert_puzzle_announcement = AssertPuzzleAnnouncement { - id: sha256(other_asset_offer_mod + tree_hash([ - proof.parent_coin_id, - [ - receiver_puzzle_hash, - if other_asset_amount > min_other_asset_amount_minus_one { - other_asset_amount - } else { - raise "Other asset amount is too small"; - }, - [receiver_puzzle_hash], - ] - ])), + id: sha256( + other_asset_offer_mod + + tree_hash( + [ + proof.parent_coin_id, + [ + receiver_puzzle_hash, + if other_asset_amount > min_other_asset_amount_minus_one { + other_asset_amount + } else { + raise "Other asset amount is too small"; + }, + [receiver_puzzle_hash], + ] + ] + ) + ), }; let assert_my_coin_id = AssertMyCoinId { @@ -55,20 +59,19 @@ fn main( ), }; - let new_amount = proof.amount - other_asset_amount * precisions.price_precision / precisions.precision; + let new_amount = proof.amount + - other_asset_amount + * precisions.price_precision + / precisions.precision; let recreate = inline if new_amount > 0 { CreateCoin { puzzle_hash: proof.inner_puzzle_hash, amount: new_amount, - memos: Memos { - value: [proof.inner_puzzle_hash], - }, + memos: Memos { value: [proof.inner_puzzle_hash], }, } } else { - Remark { - rest: nil, - } + Remark { rest: nil, } }; let inner_conditions = inner_puzzle(...inner_puzzle_solution); @@ -79,10 +82,5 @@ fn main( inner_conditions }; - [ - assert_puzzle_announcement, - assert_my_coin_id, - recreate, - ...rest, - ] + [assert_puzzle_announcement, assert_my_coin_id, recreate, ...rest,] } diff --git a/examples/puzzles/partial_offer.yaml b/examples/puzzles/partial_offer.yaml index c281f1cf..aadcafe8 100644 --- a/examples/puzzles/partial_offer.yaml +++ b/examples/puzzles/partial_offer.yaml @@ -1,5 +1,5 @@ program: (a (q 2 (q 4 (c (q . 63) (c (sha256 23 (a 5 (c 5 (c 1279 (c (c 47 (c (a (i (> 1535 -65) (q . 1535) (q 8)) 1) (c (c 47 ()) ()))) ()))))) ())) (c (c (q . 70) (c (coinid 1279 (a 11 (c 2815 6143)) 5887) ())) (c (i (> 4 ()) (c (q . 51) (c 2815 (c 4 (c (c 2815 ()) ())))) (q 1)) (i (a (i 3071 (q 2 (i (> 11263 (q . -1)) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 6) 6)))) (c (c (- 2943 (/ (* 767 319) 447)) (a 47 4095)) 1)) (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) 1)) -debug_program: (a (q 2 (q 2 (q 4 45 (c 61 (c 4 6))) (c (c (i (> 4 ()) (c (q . 51) (c (f (r 767)) (c 4 (c (c (f (r 767)) ()) ())))) (c (q . 1) ())) (i (a (i (not (not (l 3071))) (q 2 (i (> (f (r 3071)) (- () (q . 1))) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 10) 10)) 1)) (c (c (- (f (r (r 383))) (/ (* 767 (f -65)) (r -65))) (c (a 47 4095) (c (c (q . 63) (c (sha256 (concat 11 (a 2 (c 2 (c (f 383) (c (c 23 (c (a (i (> 767 95) (q . 767) (q 8 (q . "raise called at partial_offer.rue:43:21") (q . "Other asset amount is too small"))) 1) (c (c 23 ()) ()))) ())))))) ())) (c (q . 70) (c (coinid (f 383) (a 5 (c (f (r 383)) 3071)) (f (r (r 383)))) ()))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) 1)) +debug_program: (a (q 2 (q 2 (q 4 45 (c 61 (c 4 6))) (c (c (i (> 4 ()) (c (q . 51) (c (f (r 767)) (c 4 (c (c (f (r 767)) ()) ())))) (c (q . 1) ())) (i (a (i (not (not (l 3071))) (q 2 (i (> (f (r 3071)) (- () (q . 1))) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 10) 10)) 1)) (c (c (- (f (r (r 383))) (/ (* 767 (f -65)) (r -65))) (c (a 47 4095) (c (c (q . 63) (c (sha256 (concat 11 (a 2 (c 2 (c (f 383) (c (c 23 (c (a (i (> 767 95) (q . 767) (q 8 (q . "raise called at partial_offer.rue:45:33") (q . "Other asset amount is too small"))) 1) (c (c 23 ()) ()))) ())))))) ())) (c (q . 70) (c (coinid (f 383) (a 5 (c (f (r 383)) 3071)) (f (r (r 383)))) ()))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) 1)) solution: () byte_cost: 5100000 clvm_error: path into atom diff --git a/examples/puzzles/royalty_split.rue b/examples/puzzles/royalty_split.rue index d91fcc45..ff4a2939 100644 --- a/examples/puzzles/royalty_split.rue +++ b/examples/puzzles/royalty_split.rue @@ -1,5 +1,4 @@ // This puzzle has not been audited or tested, and is for example purposes only. - struct Payout { puzzle_hash: Bytes32, share: Int, @@ -52,7 +51,11 @@ fn split_amount_and_create_coins( ) -> List { let create_coin = CreateCoin { puzzle_hash: payouts.first.puzzle_hash, - amount: inline if payouts.rest is nil { remaining_amount } else { this_amount }, + amount: inline if payouts.rest is nil { + remaining_amount + } else { + this_amount + }, memos: Memos { value: [payouts.first.puzzle_hash] }, }; diff --git a/examples/puzzles/royalty_split.yaml b/examples/puzzles/royalty_split.yaml index eba267fc..7026275b 100644 --- a/examples/puzzles/royalty_split.yaml +++ b/examples/puzzles/royalty_split.yaml @@ -1,5 +1,5 @@ program: (a (q 4 (q 60 ()) (c (c (q . 73) (c 11 ())) (a 4 (c 2 (c (c 5 23) (c 11 (c () 11))))))) (c (c (q 2 (i 9 (q 2 6 (c 2 (c 9 (c (c (/ (* 11 81) 13) 31) (c 11 (c 13 23)))))) (q 2 (i (= 13 23) (q) (q 8)) 1)) 1) (q 4 (c (q . 51) (c 17 (c (i 13 19 27) (c (c 17 ()) ())))) (a 4 (c 2 (c (c 13 47) (c 23 (c (+ 63 41) (- 27 19)))))))) 1)) -debug_program: (a (q 4 (c (q . 60) (c () ())) (c (c (q . 73) (c 11 ())) (a 4 (c (c 4 (c 10 14)) (c (c 5 23) (c 11 (c () 11))))))) (c (c (q 2 (i (not (l 9)) (q 2 (i (= 13 23) (q) (q 8 (q . "raise called at royalty_split.rue:32:9") (q . "Share sum doesn't match total"))) 1) (q 2 14 (c (c 4 (c 10 14)) (c 9 (c (c (a 10 (c 11 (c (f (r (f 9))) 13))) 31) (c 11 (c 13 23))))))) 1) (c (q 19 (* 2 5) 7) (q 2 (q 4 2 (a 9 (c (c 9 (c 21 29)) (c (c (r 11) 95) (c 47 (c (+ 127 (f (r (f 11)))) (- 55 39))))))) (c (c (q . 51) (c (f (f 5)) (c (i (not (l (r 5))) 27 19) (c (c (f (f 5)) ()) ())))) 1)))) 1)) +debug_program: (a (q 4 (c (q . 60) (c () ())) (c (c (q . 73) (c 11 ())) (a 4 (c (c 4 (c 10 14)) (c (c 5 23) (c 11 (c () 11))))))) (c (c (q 2 (i (not (l 9)) (q 2 (i (= 13 23) (q) (q 8 (q . "raise called at royalty_split.rue:31:9") (q . "Share sum doesn't match total"))) 1) (q 2 14 (c (c 4 (c 10 14)) (c 9 (c (c (a 10 (c 11 (c (f (r (f 9))) 13))) 31) (c 11 (c 13 23))))))) 1) (c (q 19 (* 2 5) 7) (q 2 (q 4 2 (a 9 (c (c 9 (c 21 29)) (c (c (r 11) 95) (c 47 (c (+ 127 (f (r (f 11)))) (- 55 39))))))) (c (c (q . 51) (c (f (f 5)) (c (i (not (l (r 5))) 27 19) (c (c (f (f 5)) ()) ())))) 1)))) 1)) solution: (((0x3816a97150946c7745457f45a4fed6c16890637847b2df95f383e439758732d7 80) (0x09731fc56bff8031dc9a16f60dcb6f6462960cd76d4b5233dd5bc21697d7fca7 20)) 1000 100) output: ((modpow ()) (73 1000) (g1_negate 0x3816a97150946c7745457f45a4fed6c16890637847b2df95f383e439758732d7 800 (0x3816a97150946c7745457f45a4fed6c16890637847b2df95f383e439758732d7)) (g1_negate 0x09731fc56bff8031dc9a16f60dcb6f6462960cd76d4b5233dd5bc21697d7fca7 200 (0x09731fc56bff8031dc9a16f60dcb6f6462960cd76d4b5233dd5bc21697d7fca7))) runtime_cost: 14926 diff --git a/examples/puzzles/singleton.rue b/examples/puzzles/singleton.rue index 922f4f2f..5ea7fc20 100644 --- a/examples/puzzles/singleton.rue +++ b/examples/puzzles/singleton.rue @@ -1,5 +1,4 @@ // This puzzle has not been audited or tested, and is for example purposes only. - struct Singleton { mod_hash: Bytes32, launcher_id: Bytes32, @@ -28,25 +27,33 @@ fn main( ) -> List { // Verify the amount and parent coin id // TODO: proof is LineageProof is hand optimized here, it should be done by the compiler - let (is_lineage, parent_coin_id) = if unchecked_cast::((proof as (Any, (Any, Any))).rest.rest) { + let (is_lineage, parent_coin_id) = if unchecked_cast::( + (proof as (Any, (Any, Any))).rest.rest + ) { inline let proof = unchecked_cast::(proof); - (true, coinid( - proof.parent_parent_coin_info, - singleton_puzzle_hash(singleton, proof.parent_inner_puzzle_hash), - proof.parent_amount, - )) + ( + true, + coinid( + proof.parent_parent_coin_info, + singleton_puzzle_hash(singleton, proof.parent_inner_puzzle_hash), + proof.parent_amount, + ) + ) } else { inline let proof = unchecked_cast::(proof); - (false, coinid( - proof.parent_parent_coin_info, - singleton.launcher_puzzle_hash, - proof.parent_amount, - )) + ( + false, + coinid( + proof.parent_parent_coin_info, + singleton.launcher_puzzle_hash, + proof.parent_amount, + ) + ) }; - + let valid_amount = my_amount % 2 == 1; let valid_lineage = is_lineage | (parent_coin_id == singleton.launcher_id); - + assert valid_amount & valid_lineage; // Assert the amount and parent coin id @@ -55,11 +62,7 @@ fn main( let conditions = morph_conditions(singleton, inner_puzzle(...inner_solution), false); - [ - assert_amount, - assert_parent, - ...conditions, - ] + [assert_amount, assert_parent, ...conditions,] } inline fn singleton_puzzle_hash(singleton: Singleton, inner_puzzle_hash: Bytes32) -> Bytes32 { @@ -99,42 +102,34 @@ fn morph_conditions( } test fn tests() { - let parent_parent_coin_info = sha256("parent_parent_coin_info"); - let launcher_puzzle_hash = sha256("launcher_puzzle"); - let launcher_id = coinid(parent_parent_coin_info, launcher_puzzle_hash, 0); - - let singleton = Singleton { - mod_hash: tree_hash(main), - launcher_id, - launcher_puzzle_hash, - }; - - let inner_puzzle = fn(...solution: List) => solution; - - let proof = EveProof { - parent_parent_coin_info, - parent_amount: 0, - }; - - let p2_puzzle_hash = sha256("p2_puzzle_hash"); - - let conditions = main( - singleton, - inner_puzzle, - proof, - 1, - [CreateCoin { - puzzle_hash: p2_puzzle_hash, - amount: 1, - }], - ); - - assert tree_hash(conditions) == tree_hash([ - AssertMyAmount { amount: 1 }, - AssertMyParentId { parent_id: launcher_id }, - CreateCoin { - puzzle_hash: singleton_puzzle_hash(singleton, p2_puzzle_hash), - amount: 1, - }, - ]); + let parent_parent_coin_info = sha256("parent_parent_coin_info"); + let launcher_puzzle_hash = sha256("launcher_puzzle"); + let launcher_id = coinid(parent_parent_coin_info, launcher_puzzle_hash, 0); + + let singleton = Singleton { mod_hash: tree_hash(main), launcher_id, launcher_puzzle_hash, }; + + let inner_puzzle = fn(...solution: List) => solution; + + let proof = EveProof { parent_parent_coin_info, parent_amount: 0, }; + + let p2_puzzle_hash = sha256("p2_puzzle_hash"); + + let conditions = main( + singleton, + inner_puzzle, + proof, + 1, + [CreateCoin { puzzle_hash: p2_puzzle_hash, amount: 1, }], + ); + + assert tree_hash(conditions) == tree_hash( + [ + AssertMyAmount { amount: 1 }, + AssertMyParentId { parent_id: launcher_id }, + CreateCoin { + puzzle_hash: singleton_puzzle_hash(singleton, p2_puzzle_hash), + amount: 1, + }, + ] + ); } diff --git a/examples/puzzles/singleton.yaml b/examples/puzzles/singleton.yaml index b389c119..71d3cb5e 100644 --- a/examples/puzzles/singleton.yaml +++ b/examples/puzzles/singleton.yaml @@ -1,5 +1,5 @@ program: (a (q 2 (q 2 (i (all (logand 95 (q . 1)) (any 4 (= 6 43))) (q 4 (c (q . 73) (c 95 ())) (c (c (q . 71) (c 6 ())) (a 9 (c 5 (c 11 (c (a 23 -65) ())))))) (q 8)) 1) (c (a (i 119 (q 4 (q . 1) (coinid 39 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 9) (sha256 (q . 2) (a 14 (c 14 (c (a 10 (c 10 5)) (c 87 ())))) (sha256 (q . 1))))) -73)) (q 4 () (coinid 39 29 87))) 1) 1)) (c (c (q 2 (i 11 (q 2 (i (a (i (= 35 (q . 51)) (q 2 (i (logand -77 (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i 15 (q 8) (q 2 (q 2 (i (= 359 (q . -113)) (q . 2) (q 4 (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 19) (sha256 (q . 2) (a 29 (c 29 (c (a 21 (c 21 11)) (c -89 ())))) (sha256 (q . 1))))) -25)) 2)) 1) (c (a 4 (c 2 (c 5 (c 27 (q . 1))))) 1))) 1) (q 4 19 (a 4 (c 2 (c 5 (c 27 ())))))) 1) (q 2 (i 15 (q) (q 8)) 1)) 1) (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (q 2 (i 3 (q 11 (q . 2) (sha256 (q . 260)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 5) (sha256 (q . 2) (a 2 (c 2 7)) (sha256 (q . 1))))) (q 11 (q . 257))) 1))) 1)) -debug_program: (a (q 2 (q 2 (q 2 (i (all 13 2) (q 2 (q 4 4 (c 10 14)) (c (c (c (q . 73) (c -65 ())) (c (c (q . 71) (c (r 9) ())) (a 35 (c (c (c 35 51) (c 43 (c 91 (c -69 -5)))) (c 23 (c (a 47 383) ())))))) 1)) (q 8 (q . "assertion failed at singleton.rue:50:5"))) 1) (c (any (f 4) (= (r 4) (f (r 11)))) 1)) (c (c (a (i (r (r 23)) (q 4 (q . 1) (coinid (f 23) (a 22 (c (c 10 (c 46 62)) (c (f 5) (c (a 12 (c 12 5)) (c (f (r 23)) ()))))) (f (r (r 23))))) (q 4 () (coinid (f 23) (r (r 5)) (f (r 23))))) 1) (= (% 47 (q . 2)) (q . 1))) 1)) (c (c (c (q 2 (i (not (l 11)) (q 2 (i 15 (q) (q 8 (q . "assertion failed at singleton.rue:75:9"))) 1) (q 2 (i (a (i (= (f (f 11)) (q . 51)) (q 2 (i (= (% (f (r (r (f 11)))) (q . 2)) (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i (not 15) (q 2 (q 2 (i (= (f (r (r (f 23)))) (- () (q . 113))) (q . 2) (q 2 (q 4 2 5) (c (c (q . 51) (c (a 45 (c (c 21 (c 93 125)) (c (f 11) (c (a 25 (c 25 11)) (c (f (r (f 23))) ()))))) (c (f (r (r (f 23)))) (r (r (r (f 23))))))) 1))) 1) (c (a 8 (c (c (c 8 12) (c 10 (c 22 (c 46 62)))) (c 5 (c (r 11) (q . 1))))) 1)) (q 8 (q . "assertion failed at singleton.rue:82:9"))) 1) (q 4 (f 11) (a 8 (c (c (c 8 12) (c 10 (c 22 (c 46 62)))) (c 5 (c (r 11) ())))))) 1)) 1) (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) +debug_program: (a (q 2 (q 2 (q 2 (i (all 13 2) (q 2 (q 4 4 (c 10 14)) (c (c (c (q . 73) (c -65 ())) (c (c (q . 71) (c (r 9) ())) (a 35 (c (c (c 35 51) (c 43 (c 91 (c -69 -5)))) (c 23 (c (a 47 383) ())))))) 1)) (q 8 (q . "assertion failed at singleton.rue:57:5"))) 1) (c (any (f 4) (= (r 4) (f (r 11)))) 1)) (c (c (a (i (r (r 23)) (q 4 (q . 1) (coinid (f 23) (a 22 (c (c 10 (c 46 62)) (c (f 5) (c (a 12 (c 12 5)) (c (f (r 23)) ()))))) (f (r (r 23))))) (q 4 () (coinid (f 23) (r (r 5)) (f (r 23))))) 1) (= (% 47 (q . 2)) (q . 1))) 1)) (c (c (c (q 2 (i (not (l 11)) (q 2 (i 15 (q) (q 8 (q . "assertion failed at singleton.rue:78:9"))) 1) (q 2 (i (a (i (= (f (f 11)) (q . 51)) (q 2 (i (= (% (f (r (r (f 11)))) (q . 2)) (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i (not 15) (q 2 (q 2 (i (= (f (r (r (f 23)))) (- () (q . 113))) (q . 2) (q 2 (q 4 2 5) (c (c (q . 51) (c (a 45 (c (c 21 (c 93 125)) (c (f 11) (c (a 25 (c 25 11)) (c (f (r (f 23))) ()))))) (c (f (r (r (f 23)))) (r (r (r (f 23))))))) 1))) 1) (c (a 8 (c (c (c 8 12) (c 10 (c 22 (c 46 62)))) (c 5 (c (r 11) (q . 1))))) 1)) (q 8 (q . "assertion failed at singleton.rue:85:9"))) 1) (q 4 (f 11) (a 8 (c (c (c 8 12) (c 10 (c 22 (c 46 62)))) (c 5 (c (r 11) ())))))) 1)) 1) (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) solution: ((0x7faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9f 0x1dee7745609c0ebcf4e1cfe01f2b2e7f4deb122eb735217e7af08029ba322b13 . 0xeff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9) (a (q 2 (q 2 62 (c 2 (c 5 (c (a 47 95) (c () (c (c (c 11 (c 23 ())) (q ())) (q ()))))))) (c (q ((a . 51) 4 1 . 1) (a 2 (i 5 (q 2 26 (c 2 (c 13 (c (sha256 18 (sha256 44 20) (sha256 18 (sha256 18 (sha256 44 60) 9) (sha256 18 11 (sha256 44 ())))) ())))) (q . 11)) 1) (sha256 18 (sha256 44 16) (sha256 18 (sha256 18 (sha256 44 60) 5) (sha256 18 (a 26 (c 2 (c 7 (c (sha256 44 44) ())))) (sha256 44 ())))) (a (i (l 5) (q 11 (q . 2) (a 46 (c 2 (c 9 ()))) (a 46 (c 2 (c 13 ())))) (q 11 (q . 1) 5)) 1) 2 (i 11 (q 2 (i (= 35 24) (q 2 (i (logand -77 44) (q 2 (i (not 23) (q 2 62 (c 2 (c 5 (c 27 (c 51 (c 47 (c 95 ()))))))) (q 8)) 1) (q 4 19 (a 62 (c 2 (c 5 (c 27 (c 23 (c 47 (c 95 ()))))))))) 1) (q 2 (i (= 35 (q . -24)) (q 2 62 (c 2 (c 5 (c 27 (c 23 (c (a (i (all (= (a 46 (c 2 (c 83 ()))) 335) (not 95)) (q 2 83 (c -113 (c 335 (c -77 ())))) (q 8)) 1) (c 44 ()))))))) (q 4 19 (a 62 (c 2 (c 5 (c 27 (c 23 (c 47 (c 95 ()))))))))) 1)) 1) (q 4 (c 24 (c (a 22 (c 2 (c 5 (c 39 (c (sha256 44 335) (c (a 46 (c 2 (c -113 ()))) (c (sha256 44 5) ()))))))) 55)) -81)) 1) 1)) (c (q . 0xa04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2) (c (q (117 "https://chia-monster.ggames.mobi/chia-monster/images/warriors/females/legendary_warrior_v2.jpg") (104 . 0x9beb5d62e9708e3178414dcf7340bc3547fb6fdeadcf93a397d87dbb432bd923) (28021 "https://chia-monster.ggames.mobi/api/cmt/meta/warrior/6f1cb204-b594-4516-af4c-f9f55e30fd39.json") (27765 "https://chia-monster.ggames.mobi/chia-monster/license.txt") (29550 . 1) (29556 . 1) (28008 . 0x347340029cb9234e7a43d75d4c5649136ac11a97cd75c1137da2444868cab52b) (27752 . 0x724672b619bcbb77d46cba416327376944366d6c71e87abe49be19abbca0444f)) (c (q . 0xfe8a4b4e27a2e29a4d3fc7ce9d527adbcaccbab6ada3903ccf3ba9a769d2d78b) (c (q 2 (q 2 (q 2 38 (c 2 (c 5 (c 23 (c 11 (c (a 47 95) ())))))) (c (q ((-21172 2 . 51) (keccak256 . 4) -10 . 1) ((q . 2) (a (i 5 (q 2 42 (c 2 (c 13 (c (sha256 50 (sha256 60 52) (sha256 50 (sha256 50 (sha256 60 34) 9) (sha256 50 11 (sha256 60 ())))) ())))) (q . 11)) 1) 4 (c 56 (c (a 54 (c 2 (c 5 (c 39 (c (a 46 (c 2 (c (a (i -81 (q . -81) (q . 11)) 1) ()))) (c (sha256 60 79) (c (sha256 60 5) ()))))))) 55)) 367) ((a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c 47 (q () ())))))))) 11 50 (sha256 60 40) (sha256 50 (sha256 50 (sha256 60 34) 5) (sha256 50 (a 42 (c 2 (c 7 (c (sha256 60 60) ())))) (sha256 60 ())))) (a (i (l 5) (q 11 (q . 2) (a 46 (c 2 (c 9 ()))) (a 46 (c 2 (c 13 ())))) (q 11 (q . 1) 5)) 1) 2 (i 95 (q 2 (i (= 287 56) (q 2 (i (= (logand 1439) 60) (q 2 (i (not -65) (q 2 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c 415 (c 383 ()))))))))) (q 8)) 1) (q 4 -97 (a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c -65 (c 383 ()))))))))))) 1) (q 2 (i (= 287 44) (q 2 (i (not 383) (q 4 (c 36 (c (concat 16 (a 46 (c 2 (c 415 ())))) ())) (a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c -65 (c (a 11 (c 23 (c 47 (c 415 ())))) ())))))))))) (q 8)) 1) (q 2 (i (= 287 36) (q 2 (i (not (a (i (= (q . 34) (strlen 671)) (q 2 (i (= (substr 671 () (q . 2)) 16) (q 1 . 1) ()) 1) ()) 1)) (q 4 -97 (a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c -65 (c 383 ())))))))))) (q 8)) 1) (q 4 -97 (a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c -65 (c 383 ()))))))))))) 1)) 1)) 1) (q 2 58 (c 2 (c 5 (c 11 (c -65 (c (a (i 383 (q . 383) (q 2 11 (c 23 (c 47 (q ()))))) 1) ()))))))) 1) 1)) (c (q . 0xc5abea79afaa001b5427dfa0c8cf42ca6f38f5841b78f9b3c252733eb2de2726) (c (q) (c (q 2 (q 2 (q 2 (i -65 (q 4 319 (c () (c (a (i (all 319 (not (= 319 47))) (q 4 (c 16 (c (sha256 (a 46 (c 2 (c 9 (c 1471 (c (a 62 (c 2 (c (c 9 (c 319 29)) ()))) ()))))) 21) ())) (a 22 (c 2 (c 11 (c 23 (c 703 (c 21 ()))))))) (q 2 22 (c 2 (c 11 (c 23 (c 703 (c 21 ()))))))) 1) ()))) (q 4 47 (q () ()))) 1) (c (q ((63 . 2) 4 1 . 1) (10000 2 2 (i 5 (q 2 58 (c 2 (c 13 (c (sha256 42 (sha256 44 20) (sha256 42 (sha256 42 (sha256 44 60) 9) (sha256 42 11 (sha256 44 ())))) ())))) (q . 11)) 1) (a (i 23 (q 4 (c 16 (c (sha256 -89 (a 62 (c 2 (c (c 47 (c (c 5 (c (f (divmod (* 71 11) 18)) (c (c 5 ()) ()))) ())) ())))) ())) (a 22 (c 2 (c 5 (c 11 (c 55 (c 47 ()))))))) ()) 1) (sha256 42 (sha256 44 24) (sha256 42 (sha256 42 (sha256 44 60) 5) (sha256 42 (a 58 (c 2 (c 7 (c (sha256 44 44) ())))) (sha256 44 ())))) 2 (i (l 5) (q 11 (q . 2) (a 62 (c 2 (c 9 ()))) (a 62 (c 2 (c 13 ())))) (q 11 (q . 1) 5)) 1) 1)) (c (q 0x7faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9f 0x1dee7745609c0ebcf4e1cfe01f2b2e7f4deb122eb735217e7af08029ba322b13 . 0xeff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9) (c (q . 0x15865265653a7cb6f807a227aa9ad05498babe13ec906155b2676d5f92f84d40) (c (q) 1)))) (c (q 2 (q 2 (q 2 (i 11 (q 2 (i (= 5 (point_add 11 (pubkey_for_exp (sha256 11 (a 6 (c 2 (c 23 ()))))))) (q 2 23 47) (q 8)) 1) (q 4 (c 4 (c 5 (c (a 6 (c 2 (c 23 ()))) ()))) (a 23 47))) 1) (c (q 50 2 (i (l 5) (q 11 (q . 2) (a 6 (c 2 (c 9 ()))) (a 6 (c 2 (c 13 ())))) (q 11 (q . 1) 5)) 1) 1)) (c (q . 0xafa76c68cfb723922e67dbfa6bda38fd64609b41d2a4498df49a6c839ed8b2d1c042f5961a2ab9b318cc64aef72364a5) 1)) 1))))) 1))))) (0xdebedabb069cb2564157ff1ca5aa80b3cb3d7eb930f52ff1a40b8b78c84f4125 0x9fc9c96a5f2417e8617ea847651ceaf4789e6528a55101457d120c630a8ac030 1) 1 (((() (q (g1_negate 0x291c295548f95c2462caa05165f67e9f6d806153856edcbb84290162e7f810c5 1 (0x291c295548f95c2462caa05165f67e9f6d806153856edcbb84290162e7f810c5)) (-10 () () ())) ())))) output: ((73 1) (71 0x82acf3e76d5657e98438ad4dc0fcaf84f8e194d78313206ead021e2d5afe5e3a) (g1_multiply 0xafa76c68cfb723922e67dbfa6bda38fd64609b41d2a4498df49a6c839ed8b2d1c042f5961a2ab9b318cc64aef72364a5 0x8f25313e125d83841401ae4bf3831b674256c0fa77c8b30f12dd5a7bce846e75) (keccak256 0xad4c53b883241fddfd1c01b608bd4b6633e3fefd950e27c194aab24e6eb0a49bebd2) (g1_negate 0x899b958268f46b62cc8d2148fef14ce74e8220c507ee9a06a78924fb9efc4d75 1 (0x291c295548f95c2462caa05165f67e9f6d806153856edcbb84290162e7f810c5))) runtime_cost: 1370969 @@ -8,7 +8,7 @@ total_cost: 11270969 tests: - name: tests program: (a (q 2 (q 2 (q 2 (q 2 (i (= (a 39 (c 39 (a -73 (c 87 (c 39 (c -9 (c 2 (c (q . 1) (c (c 19 (q ())) (c (q . 1) (c (c (c (q . 51) (c 59 (q 1))) ()) ()))))))))))) (a 39 (c 39 (c (q 73 1) (c (c (q . 71) (c 5 ())) (c (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 4) (sha256 (q . 2) (a -9 (c -9 (c (a 39 (c 39 2)) (c 59 ())))) (sha256 (q . 1))))) (q 1))) ())))))) (q) (q 8)) 1) (c (c (a 19 (c 19 (c (q . 2) (c (c (q . 1) 91) (c (c (q . 4) (c (c (q . 1) 43) (c (c (q . 4) (c (c (q . 1) 19) (c (c (q . 4) (c (c (q . 1) 123) (c (q . 1) ()))) ()))) ()))) ()))))) (c 2 21)) 1)) (c (coinid 4 10 ()) 1)) (c (c (sha256 (q . "parent_parent_coin_info")) (c (sha256 (q . "launcher_puzzle")) (sha256 (q . "p2_puzzle_hash")))) 1)) (c (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (c (q 2 (i 11 (q 2 (i (a (i (= 35 (q . 51)) (q 2 (i (logand -77 (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i 15 (q 8) (q 2 (q 2 (i (= 359 (q . -113)) (q . 2) (q 4 (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 19) (sha256 (q . 2) (a 29 (c 29 (c (a 9 (c 9 11)) (c -89 ())))) (sha256 (q . 1))))) -25)) 2)) 1) (c (a 10 (c 2 (c 5 (c 27 (q . 1))))) 1))) 1) (q 4 19 (a 10 (c 2 (c 5 (c 27 ())))))) 1) (q 2 (i 15 (q) (q 8)) 1)) 1) (c (q 2 (q 2 (i (all (logand 383 (q . 1)) (any 4 (= 6 -81))) (q 4 (c (q . 73) (c 383 ())) (c (c (q . 71) (c 6 ())) (a 5 (c (c 11 (c 5 23)) (c 47 (c (a 95 767) ())))))) (q 8)) 1) (c (a (i 479 (q 4 (q . 1) (coinid -97 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 39) (sha256 (q . 2) (a 11 (c 11 (c (a 5 (c 5 23)) (c 351 ())))) (sha256 (q . 1))))) 735)) (q 4 () (coinid -97 119 351))) 1) 1)) (q 2 (i 3 (q 11 (q . 2) (sha256 (q . 260)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 5) (sha256 (q . 2) (a 2 (c 2 7)) (sha256 (q . 1))))) (q 11 (q . 257))) 1)))) ())) - debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (i (= (a -113 (c -113 2)) (a -113 (c -113 (c (c (q . 73) (c (q . 1) ())) (c (c (q . 71) (c 19 ())) (c (c (q . 51) (c (a 367 (c (c 431 (c 751 1007)) (c (f 5) (c (a -113 (c -113 5)) (c 87 ()))))) (c (q . 1) ()))) ())))))) (q) (q 8 (q . "assertion failed at singleton.rue:132:3"))) 1) (c (a -105 (c 103 (c 71 (c -73 (c -41 (c 375 (c 503 (c 2 (c 59 (c 13 (c (q . 1) (c (c (c (q . 51) (c 43 (c (q . 1) ()))) ()) ())))))))))))) 1)) (c (c (a 35 (c 35 (c (q . 2) (c (c (q . 1) 75) (c (c (q . 4) (c (c (q . 1) 51) (c (c (q . 4) (c (c (q . 1) 35) (c (c (q . 4) (c (c (q . 1) 91) (c (c (q . 4) (c (c (q . 1) 107) (c (c (q . 4) (c (c (q . 1) -69) (c (c (q . 4) (c (c (q . 1) -5) (c (q . 1) ()))) ()))) ()))) ()))) ()))) ()))) ()))))) (c 4 25)) 1)) (c (c (coinid 8 12 ()) (c 8 (c () ()))) 1)) (c (c (c (sha256 (q . "parent_parent_coin_info")) (sha256 (q . "launcher_puzzle"))) (c (sha256 (q . "p2_puzzle_hash")) (c (q . 2) (c (c (q . 1) (q . 1)) (c (q . 1) ()))))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 11)) (q 2 (i 15 (q) (q 8 (q . "assertion failed at singleton.rue:75:9"))) 1) (q 2 (i (a (i (= (f (f 11)) (q . 51)) (q 2 (i (= (% (f (r (r (f 11)))) (q . 2)) (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i (not 15) (q 2 (q 2 (i (= (f (r (r (f 23)))) (- () (q . 113))) (q . 2) (q 2 (q 4 2 5) (c (c (q . 51) (c (a 93 (c (c 45 (c -67 -3)) (c (f 11) (c (a 9 (c 9 11)) (c (f (r (f 23))) ()))))) (c (f (r (r (f 23)))) (r (r (r (f 23))))))) 1))) 1) (c (a 10 (c (c 4 (c 10 (c 22 (c 46 (c 94 126))))) (c 5 (c (r 11) (q . 1))))) 1)) (q 8 (q . "assertion failed at singleton.rue:82:9"))) 1) (q 4 (f 11) (a 10 (c (c 4 (c 10 (c 22 (c 46 (c 94 126))))) (c 5 (c (r 11) ())))))) 1)) 1)) (c (c (q 2 (q 2 (q 2 (i (all 13 2) (q 2 (q 4 4 (c 10 14)) (c (c (c (q . 73) (c 6143 ())) (c (c (q . 71) (c (r 9) ())) (a 11 (c (c 23 (c 11 (c 95 (c 47 (c -65 383))))) (c 767 (c (a 1535 12287) ())))))) 1)) (q 8 (q . "assertion failed at singleton.rue:50:5"))) 1) (c (any (f 4) (= (r 4) (f (r 383)))) 1)) (c (c (a (i (r (r 767)) (q 4 (q . 1) (coinid (f 767) (a 11 (c (c 23 (c 47 95)) (c (f -65) (c (a 5 (c 5 -65)) (c (f (r 767)) ()))))) (f (r (r 767))))) (q 4 () (coinid (f 767) (r (r -65)) (f (r 767))))) 1) (= (% 1535 (q . 2)) (q . 1))) 1)) (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) ())) + debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (i (= (a -113 (c -113 2)) (a -113 (c -113 (c (c (q . 73) (c (q . 1) ())) (c (c (q . 71) (c 19 ())) (c (c (q . 51) (c (a 367 (c (c 431 (c 751 1007)) (c (f 5) (c (a -113 (c -113 5)) (c 87 ()))))) (c (q . 1) ()))) ())))))) (q) (q 8 (q . "assertion failed at singleton.rue:125:5"))) 1) (c (a -105 (c 103 (c 71 (c -73 (c -41 (c 375 (c 503 (c 2 (c 59 (c 13 (c (q . 1) (c (c (c (q . 51) (c 43 (c (q . 1) ()))) ()) ())))))))))))) 1)) (c (c (a 35 (c 35 (c (q . 2) (c (c (q . 1) 75) (c (c (q . 4) (c (c (q . 1) 51) (c (c (q . 4) (c (c (q . 1) 35) (c (c (q . 4) (c (c (q . 1) 91) (c (c (q . 4) (c (c (q . 1) 107) (c (c (q . 4) (c (c (q . 1) -69) (c (c (q . 4) (c (c (q . 1) -5) (c (q . 1) ()))) ()))) ()))) ()))) ()))) ()))) ()))))) (c 4 25)) 1)) (c (c (coinid 8 12 ()) (c 8 (c () ()))) 1)) (c (c (c (sha256 (q . "parent_parent_coin_info")) (sha256 (q . "launcher_puzzle"))) (c (sha256 (q . "p2_puzzle_hash")) (c (q . 2) (c (c (q . 1) (q . 1)) (c (q . 1) ()))))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 11)) (q 2 (i 15 (q) (q 8 (q . "assertion failed at singleton.rue:78:9"))) 1) (q 2 (i (a (i (= (f (f 11)) (q . 51)) (q 2 (i (= (% (f (r (r (f 11)))) (q . 2)) (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i (not 15) (q 2 (q 2 (i (= (f (r (r (f 23)))) (- () (q . 113))) (q . 2) (q 2 (q 4 2 5) (c (c (q . 51) (c (a 93 (c (c 45 (c -67 -3)) (c (f 11) (c (a 9 (c 9 11)) (c (f (r (f 23))) ()))))) (c (f (r (r (f 23)))) (r (r (r (f 23))))))) 1))) 1) (c (a 10 (c (c 4 (c 10 (c 22 (c 46 (c 94 126))))) (c 5 (c (r 11) (q . 1))))) 1)) (q 8 (q . "assertion failed at singleton.rue:85:9"))) 1) (q 4 (f 11) (a 10 (c (c 4 (c 10 (c 22 (c 46 (c 94 126))))) (c 5 (c (r 11) ())))))) 1)) 1)) (c (c (q 2 (q 2 (q 2 (i (all 13 2) (q 2 (q 4 4 (c 10 14)) (c (c (c (q . 73) (c 6143 ())) (c (c (q . 71) (c (r 9) ())) (a 11 (c (c 23 (c 11 (c 95 (c 47 (c -65 383))))) (c 767 (c (a 1535 12287) ())))))) 1)) (q 8 (q . "assertion failed at singleton.rue:57:5"))) 1) (c (any (f 4) (= (r 4) (f (r 383)))) 1)) (c (c (a (i (r (r 767)) (q 4 (q . 1) (coinid (f 767) (a 11 (c (c 23 (c 47 95)) (c (f -65) (c (a 5 (c 5 -65)) (c (f (r 767)) ()))))) (f (r (r 767))))) (q 4 () (coinid (f 767) (r (r -65)) (f (r 767))))) 1) (= (% 1535 (q . 2)) (q . 1))) 1)) (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) ())) output: () runtime_cost: 1288216 byte_cost: 17160000 diff --git a/tests/blocks/if_stmt.rue b/tests/blocks/if_stmt.rue index b74fd9ef..1a6f2283 100644 --- a/tests/blocks/if_stmt.rue +++ b/tests/blocks/if_stmt.rue @@ -1,6 +1,5 @@ fn main() -> Int { if 42 > 10 { return 123; - } - 100 + } 100 } diff --git a/tests/builtins.rue b/tests/builtins.rue index c802fb5d..93915e01 100644 --- a/tests/builtins.rue +++ b/tests/builtins.rue @@ -4,20 +4,30 @@ test fn casting() { let noop = unchecked_cast::(...value: T) -> T>(1); assert noop(...42) == 42; - let first = unchecked_cast::(...value: (A, B)) -> A>(2); - let rest = unchecked_cast::(...value: (A, B)) -> B>(3); + let first = unchecked_cast::(...value: (A, B)) -> A>(2); + let rest = unchecked_cast::(...value: (A, B)) -> B>(3); assert first(...("hello", "world")) == "hello"; assert rest(...("hello", "world")) == "world"; } test fn hash_functions() { assert sha256("hello") == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; - assert sha256(...["hel", "lo"]) == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; - assert sha256_inline("hello") == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; + assert sha256( + ...["hel", "lo"] + ) == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; + assert sha256_inline( + "hello" + ) == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; assert keccak256("hello") == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; - assert keccak256(...["hel", "lo"]) == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; - assert keccak256_inline("hello") == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; + assert keccak256( + ...["hel", "lo"] + ) == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; + assert keccak256_inline( + "hello" + ) == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; let parent_coin_id = sha256("parent_coin_id"); let puzzle_hash = sha256("puzzle_hash"); @@ -75,7 +85,9 @@ test fn g1_math() { let sum = 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5; assert pubkey_for_exp(unchecked_cast::(0)) == INFINITY_G1; - assert pubkey_for_exp(0x0000000000000000000000000000000000000000000000000000000000000000) == INFINITY_G1; + assert pubkey_for_exp( + 0x0000000000000000000000000000000000000000000000000000000000000000 + ) == INFINITY_G1; assert pubkey_for_exp(sha256(nil)) == pk; assert g1_sum([]) == INFINITY_G1; diff --git a/tests/builtins.yaml b/tests/builtins.yaml index 4cd144f2..098957be 100644 --- a/tests/builtins.yaml +++ b/tests/builtins.yaml @@ -1,49 +1,49 @@ tests: - name: casting program: (a (i (= (q . "hello") (q . "hello")) (q 2 (i (= (a (q . 1) (q . 42)) (q . 42)) (q 2 (i (= (a (q . 2) (q "hello" . "world")) (q . "hello")) (q 2 (i (= (a (q . 3) (q "hello" . "world")) (q . "world")) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) - debug_program: (a (i (= (q . "hello") (q . "hello")) (q 2 (q 2 (i (= (a 2 (q . 42)) (q . 42)) (q 2 (q 2 (i (= (a 4 (c (q . "hello") (q . "world"))) (q . "hello")) (q 2 (i (= (a 6 (c (q . "hello") (q . "world"))) (q . "world")) (q) (q 8 (q . "assertion failed at builtins.rue:10:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:9:5"))) 1) (c (c (q . 2) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:5:5"))) 1) (c (q . 1) 1)) (q 8 (q . "assertion failed at builtins.rue:2:5"))) 1) + debug_program: (a (i (= (q . "hello") (q . "hello")) (q 2 (q 2 (i (= (a 2 (q . 42)) (q . 42)) (q 2 (q 2 (i (= (a 4 (c (q . "hello") (q . "world"))) (q . "hello")) (q 2 (i (= (a 6 (c (q . "hello") (q . "world"))) (q . "world")) (q) (q 8 (q . "assertion failed at builtins.rue:12:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:11:5"))) 1) (c (c (q . 2) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:5:5"))) 1) (c (q . 1) 1)) (q 8 (q . "assertion failed at builtins.rue:2:5"))) 1) output: () runtime_cost: 2017 byte_cost: 2268000 total_cost: 2270017 - name: hash_functions program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (q "hel" 27759)) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (q "hel" 27759)) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 9 21 29)) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8)) 1) (q 8)) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) - debug_program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 (concat (concat 9 21) 29))) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8 (q . "assertion failed at builtins.rue:28:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:27:5"))) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8 (q . "assertion failed at builtins.rue:20:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:19:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:18:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:16:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:15:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:14:5"))) 1) + debug_program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 (concat (concat 9 21) 29))) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8 (q . "assertion failed at builtins.rue:38:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:37:5"))) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8 (q . "assertion failed at builtins.rue:28:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:25:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:24:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:20:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:17:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:16:5"))) 1) output: () runtime_cost: 10577 byte_cost: 8328000 total_cost: 8338577 - name: byte_manipulation program: (a (i (a (c (c (q . 14) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 14) ()) (q "hello")) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (q "hel" 27759)) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 14) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hello") ())) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8 (q . "assertion failed at builtins.rue:38:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:37:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:36:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:34:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:33:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:32:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 14) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hello") ())) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8 (q . "assertion failed at builtins.rue:48:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:47:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:46:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:44:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:43:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:42:5"))) 1) output: () runtime_cost: 4460 byte_cost: 3720000 total_cost: 3724460 - name: arithmetic program: (a (i (a (c (c (q . 16) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 16) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (q 1 2)) ()) (q . 3)) (q 2 (i (a (c (c (q . 17) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 17) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (q 1 2)) ()) (q . -1)) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (q 2 3)) ()) (q . 6)) (q 2 (q 2 (i (= 4 (q . 3)) (q 2 (i (= 6 (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 3 . 1) 1)) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1) (q 8)) 1) (q 8)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 16) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) (c (q . 2) ()))) ()) (q . 3)) (q 2 (i (= (a (c (c (q . 17) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) (c (q . 2) ()))) ()) (- () (q . 1))) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 2) (c (q . 3) ()))) ()) (q . 6)) (q 2 (q 2 (i (= (f 2) (q . 3)) (q 2 (i (= (r 2) (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8 (q . "assertion failed at builtins.rue:58:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:56:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:55:5"))) 1) (c (divmod (q . 10) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:52:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:51:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:50:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:48:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:47:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:46:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:44:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:43:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:42:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 16) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) (c (q . 2) ()))) ()) (q . 3)) (q 2 (i (= (a (c (c (q . 17) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) (c (q . 2) ()))) ()) (- () (q . 1))) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 2) (c (q . 3) ()))) ()) (q . 6)) (q 2 (q 2 (i (= (f 2) (q . 3)) (q 2 (i (= (r 2) (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8 (q . "assertion failed at builtins.rue:68:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:66:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:65:5"))) 1) (c (divmod (q . 10) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:62:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:61:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:60:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:58:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:57:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:56:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:54:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:53:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:52:5"))) 1) output: () runtime_cost: 28961 byte_cost: 6768000 total_cost: 6796961 - name: boolean_logic program: (a (i (a (c (c (q . 33) ()) ()) ()) (q 8) (q 2 (i (a (c (c (q . 33) ()) (q ())) ()) (q 8) (q 2 (i (= (a (c (c (q . 33) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (q () 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (a (c (c (q . 34) ()) (q ())) ()) (q 8) (q 2 (i (= (a (c (c (q . 34) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (q 1 1)) ()) (q . 1)) (q) (q 8)) 1) (q 8)) 1)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 33) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (c () (c (q . 1) ()))) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) (c (q . 1) ()))) ()) (q . 1)) (q) (q 8 (q . "assertion failed at builtins.rue:70:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:69:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:68:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:67:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:65:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:64:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:63:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:62:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 33) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (c () (c (q . 1) ()))) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) (c (q . 1) ()))) ()) (q . 1)) (q) (q 8 (q . "assertion failed at builtins.rue:80:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:79:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:78:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:77:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:75:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:74:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:73:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:72:5"))) 1) output: () runtime_cost: 9752 byte_cost: 4668000 total_cost: 4677752 - name: g1_math program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5) 1)) (c (point_add) ())) - debug_program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:93:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:92:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:91:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:90:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:89:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:88:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:87:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:85:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:84:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:83:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:82:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:81:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:79:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:78:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:77:5"))) 1) (c (c (q . 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b) (q . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5)) 1)) (c (point_add) ())) + debug_program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:105:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:104:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:103:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:102:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:101:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:100:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:99:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:97:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:96:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:95:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:94:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:93:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:91:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:88:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:87:5"))) 1) (c (c (q . 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b) (q . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5)) 1)) (c (point_add) ())) output: () runtime_cost: 28164410 byte_cost: 10956000 total_cost: 39120410 - name: g2_math program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063 . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a) 1)) (c (g2_add) ())) - debug_program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:112:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:111:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:110:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:109:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:108:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:107:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:106:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:104:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:103:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:102:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:101:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:100:5"))) 1) (c (c (q . 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063) (q . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a)) 1)) (c (g2_add) ())) + debug_program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:124:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:123:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:122:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:121:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:120:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:119:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:118:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:116:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:115:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:114:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:113:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:112:5"))) 1) (c (c (q . 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063) (q . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a)) 1)) (c (g2_add) ())) output: () runtime_cost: 34216780 byte_cost: 10596000 diff --git a/tests/expressions/blocks.rue b/tests/expressions/blocks.rue index 1e29a0d2..eb10c98b 100644 --- a/tests/expressions/blocks.rue +++ b/tests/expressions/blocks.rue @@ -1,19 +1,25 @@ test fn simple() -> Int { - { 42 } + { + 42 + } } test fn capture() -> Int { let value = 42; - { value * 2 } + { + value * 2 + } } test fn shadow() -> Int { let value = 42; - value + { - let original = value; - let value = 34; - original * value - } + value + value + + { + let original = value; + let value = 34; + original * value + } + + value } test fn binding() -> Int { @@ -25,5 +31,11 @@ test fn binding() -> Int { } test fn nested() -> Int { - {{{ 42 }}} + { + { + { + 42 + } + } + } } diff --git a/tests/expressions/const_expr.rue b/tests/expressions/const_expr.rue index 6d739233..ebde5d4d 100644 --- a/tests/expressions/const_expr.rue +++ b/tests/expressions/const_expr.rue @@ -1,11 +1,15 @@ const BASE: Int = 5; test fn forward_const_function() -> Int { - const { later_const() } + const { + later_const() + } } fn later_const() -> Int { - const { 42 } + const { + 42 + } } fn double(value: Int) -> Int { @@ -21,7 +25,9 @@ fn factorial(value: Int) -> Int { } test fn literal() -> Int { - const { 1 + 2 } + const { + 1 + 2 + } } test fn block() -> Int { @@ -32,27 +38,42 @@ test fn block() -> Int { } test fn pair() -> (Int, Int) { - const { (1, 2) } + const { + (1, 2) + } } test fn list() -> List { - const { [1, 2, 3] } + const { + [1, 2, 3] + } } test fn const_item() -> Int { - const { BASE * 2 } + const { + BASE * 2 + } } test fn function() -> Int { - const { double(BASE) } + const { + double(BASE) + } } test fn recursive_function() -> Int { - const { factorial(5) } + const { + factorial(5) + } } test fn nested() -> Int { - const { const { 1 + 2 } + 3 } + const { + const { + 1 + 2 + } + + 3 + } } test fn debug_effect() -> Int { diff --git a/tests/expressions/const_expr_errors.rue b/tests/expressions/const_expr_errors.rue index 02343cca..5a11493e 100644 --- a/tests/expressions/const_expr_errors.rue +++ b/tests/expressions/const_expr_errors.rue @@ -1,10 +1,14 @@ test fn parameter(value: Int) -> Int { - const { value + 1 } + const { + value + 1 + } } test fn outer_binding() -> Int { let value = 1; - const { value + 1 } + const { + value + 1 + } } test fn raises() -> Int { diff --git a/tests/expressions/const_expr_errors.yaml b/tests/expressions/const_expr_errors.yaml index 60166d4a..4f1979c9 100644 --- a/tests/expressions/const_expr_errors.yaml +++ b/tests/expressions/const_expr_errors.yaml @@ -1,4 +1,4 @@ diagnostics: - Const expression depends on runtime value `value` at const_expr_errors.rue:2:5 -- Const expression depends on runtime value `value` at const_expr_errors.rue:7:5 -- 'Const expression failed to evaluate: clvm raise at const_expr_errors.rue:11:5' +- Const expression depends on runtime value `value` at const_expr_errors.rue:9:5 +- 'Const expression failed to evaluate: clvm raise at const_expr_errors.rue:15:5' diff --git a/tests/expressions/const_expr_unresolved.rue b/tests/expressions/const_expr_unresolved.rue index 15df821d..413e36fd 100644 --- a/tests/expressions/const_expr_unresolved.rue +++ b/tests/expressions/const_expr_unresolved.rue @@ -1,3 +1,5 @@ test fn unresolved() -> Int { - const { missing } + const { + missing + } } diff --git a/tests/expressions/const_expr_unresolved.yaml b/tests/expressions/const_expr_unresolved.yaml index ab273357..73a1d9b5 100644 --- a/tests/expressions/const_expr_unresolved.yaml +++ b/tests/expressions/const_expr_unresolved.yaml @@ -1,2 +1,2 @@ diagnostics: -- Undeclared symbol `missing` at const_expr_unresolved.rue:2:13 +- Undeclared symbol `missing` at const_expr_unresolved.rue:3:9 diff --git a/tests/expressions/if_else.rue b/tests/expressions/if_else.rue index 645779eb..c1d5dcee 100644 --- a/tests/expressions/if_else.rue +++ b/tests/expressions/if_else.rue @@ -7,11 +7,13 @@ test fn lazy_if(value: Int) -> String { } test fn eager_if_grouped(value: Int) -> String { - (inline if value > 100 { - "Large" - } else { - "Small" - }) + ( + inline if value > 100 { + "Large" + } else { + "Small" + } + ) } test fn eager_if_ungrouped(value: Int) -> String { diff --git a/tests/external/external_main.rue b/tests/external/external_main.rue index c3c0a6af..765c6637 100644 --- a/tests/external/external_main.rue +++ b/tests/external/external_main.rue @@ -1,2 +1 @@ -extern fn main(a: Int, b: Int) -> Int - from "./add.hex"; +extern fn main(a: Int, b: Int) -> Int from "./add.hex"; diff --git a/tests/external/signature_types.rue b/tests/external/signature_types.rue index c85471f1..95157a8c 100644 --- a/tests/external/signature_types.rue +++ b/tests/external/signature_types.rue @@ -1,5 +1,4 @@ -extern fn external(value: T, proof: Proof) -> Any - from "./add.hex"; +extern fn external(value: T, proof: Proof) -> Any from "./add.hex"; struct FirstProof { value: Int, @@ -12,5 +11,7 @@ struct SecondProof { type Proof = FirstProof | SecondProof; fn main() -> Bytes32 { - const { tree_hash(external) } + const { + tree_hash(external) + } } diff --git a/tests/imports/duplicate_imports.rue b/tests/imports/duplicate_imports.rue index d13a5eed..2cf067f3 100644 --- a/tests/imports/duplicate_imports.rue +++ b/tests/imports/duplicate_imports.rue @@ -2,7 +2,7 @@ mod a { export fn thing() -> Int { 42 } - + export thing; export thing; } diff --git a/tests/imports/import_order.rue b/tests/imports/import_order.rue index c17e463f..41be92c7 100644 --- a/tests/imports/import_order.rue +++ b/tests/imports/import_order.rue @@ -23,6 +23,7 @@ mod b { } import a::hello_world_a; + import b::hello_world_b; test fn a_entrypoint() -> String { diff --git a/tests/imports/unresolved_imports.rue b/tests/imports/unresolved_imports.rue index 525f8732..053bd37f 100644 --- a/tests/imports/unresolved_imports.rue +++ b/tests/imports/unresolved_imports.rue @@ -1,5 +1,7 @@ mod module {} import module::missing; + import module::inner::missing; + import module::inner::*; diff --git a/tests/imports/unresolved_imports.yaml b/tests/imports/unresolved_imports.yaml index ba33c32e..e42294b1 100644 --- a/tests/imports/unresolved_imports.yaml +++ b/tests/imports/unresolved_imports.yaml @@ -1,4 +1,4 @@ diagnostics: -- Undeclared symbol `inner` at unresolved_imports.rue:4:16 - Undeclared symbol `inner` at unresolved_imports.rue:5:16 +- Undeclared symbol `inner` at unresolved_imports.rue:7:16 - Unresolved import `missing` at unresolved_imports.rue:3:16 diff --git a/tests/optimizer/function_value.rue b/tests/optimizer/function_value.rue index 1d50ef92..755570b1 100644 --- a/tests/optimizer/function_value.rue +++ b/tests/optimizer/function_value.rue @@ -9,7 +9,7 @@ fn main() { // So the output of this will be different depending on the build type let actual = tree_hash(fn(a: Int, b: Int) => a + b); - + // However, this test should pass in both debug and optimized builds assert actual == debug_hash || actual == optimized_hash; } diff --git a/tests/projects/external_import/main.rue b/tests/projects/external_import/main.rue index 07db7c46..9256dac3 100644 --- a/tests/projects/external_import/main.rue +++ b/tests/projects/external_import/main.rue @@ -1,10 +1,8 @@ import nested::helper::add_nested; -extern fn add(a: Int, b: Int) -> Int - from "./add.hex"; +extern fn add(a: Int, b: Int) -> Int from "./add.hex"; -extern fn add_again(a: Int, b: Int) -> Int - from "./add.hex"; +extern fn add_again(a: Int, b: Int) -> Int from "./add.hex"; test fn call_external() -> Int { add(20, 22) diff --git a/tests/projects/external_import/nested/helper.rue b/tests/projects/external_import/nested/helper.rue index 915d420f..1cfd98f4 100644 --- a/tests/projects/external_import/nested/helper.rue +++ b/tests/projects/external_import/nested/helper.rue @@ -1,2 +1 @@ -export extern fn add_nested(a: Int, b: Int) -> Int - from "../add.hex"; +export extern fn add_nested(a: Int, b: Int) -> Int from "../add.hex"; diff --git a/tests/std.rue b/tests/std.rue index ade799b8..4b952c4a 100644 --- a/tests/std.rue +++ b/tests/std.rue @@ -8,9 +8,18 @@ test fn condition_type_guards() { assert AggSigParent { message: nil, public_key: INFINITY_G1 } as Condition is AggSigParent; assert AggSigPuzzle { message: nil, public_key: INFINITY_G1 } as Condition is AggSigPuzzle; assert AggSigAmount { message: nil, public_key: INFINITY_G1 } as Condition is AggSigAmount; - assert AggSigPuzzleAmount { message: nil, public_key: INFINITY_G1 } as Condition is AggSigPuzzleAmount; - assert AggSigParentAmount { message: nil, public_key: INFINITY_G1 } as Condition is AggSigParentAmount; - assert AggSigParentPuzzle { message: nil, public_key: INFINITY_G1 } as Condition is AggSigParentPuzzle; + assert AggSigPuzzleAmount { + message: nil, + public_key: INFINITY_G1 + } as Condition is AggSigPuzzleAmount; + assert AggSigParentAmount { + message: nil, + public_key: INFINITY_G1 + } as Condition is AggSigParentAmount; + assert AggSigParentPuzzle { + message: nil, + public_key: INFINITY_G1 + } as Condition is AggSigParentPuzzle; assert AggSigUnsafe { message: nil, public_key: INFINITY_G1 } as Condition is AggSigUnsafe; assert AggSigMe { message: nil, public_key: INFINITY_G1 } as Condition is AggSigMe; assert CreateCoin { puzzle_hash: puzzle_hash, amount: 42 } as Condition is CreateCoin; @@ -20,7 +29,9 @@ test fn condition_type_guards() { assert CreatePuzzleAnnouncement { message: nil } as Condition is CreatePuzzleAnnouncement; assert AssertPuzzleAnnouncement { id: id } as Condition is AssertPuzzleAnnouncement; assert AssertConcurrentSpend { coin_id: coin_id } as Condition is AssertConcurrentSpend; - assert AssertConcurrentPuzzle { puzzle_hash: puzzle_hash } as Condition is AssertConcurrentPuzzle; + assert AssertConcurrentPuzzle { + puzzle_hash: puzzle_hash + } as Condition is AssertConcurrentPuzzle; assert SendMessage { mode: 42, message: nil, receiver: [42] } as Condition is SendMessage; assert ReceiveMessage { mode: 42, message: nil, sender: [42] } as Condition is ReceiveMessage; assert AssertMyCoinId { coin_id: coin_id } as Condition is AssertMyCoinId; @@ -29,7 +40,7 @@ test fn condition_type_guards() { assert AssertMyAmount { amount: 42 } as Condition is AssertMyAmount; assert AssertMyBirthSeconds { seconds: 42 } as Condition is AssertMyBirthSeconds; assert AssertMyBirthHeight { height: 42 } as Condition is AssertMyBirthHeight; - assert AssertEphemeral { } as Condition is AssertEphemeral; + assert AssertEphemeral {} as Condition is AssertEphemeral; assert AssertSecondsRelative { seconds: 42 } as Condition is AssertSecondsRelative; assert AssertSecondsAbsolute { seconds: 42 } as Condition is AssertSecondsAbsolute; assert AssertHeightRelative { height: 42 } as Condition is AssertHeightRelative; @@ -38,7 +49,7 @@ test fn condition_type_guards() { assert AssertBeforeSecondsAbsolute { seconds: 42 } as Condition is AssertBeforeSecondsAbsolute; assert AssertBeforeHeightRelative { height: 42 } as Condition is AssertBeforeHeightRelative; assert AssertBeforeHeightAbsolute { height: 42 } as Condition is AssertBeforeHeightAbsolute; - assert Softfork { cost: 42, args: []} as Condition is Softfork; + assert Softfork { cost: 42, args: [] } as Condition is Softfork; } test fn hashing() { @@ -53,14 +64,9 @@ test fn currying() { } test fn recursion() { - check_each([ - nil, - "Hello, world!", - (100, 200), - [1, 2, 3], - fn(a: Int, b: Int) => a + b, - check_each, - ]); + check_each( + [nil, "Hello, world!", (100, 200), [1, 2, 3], fn(a: Int, b: Int) => a + b, check_each,] + ); } fn check_each(list: List) { diff --git a/tests/std.yaml b/tests/std.yaml index c5d0ed3f..4d7ad6fa 100644 --- a/tests/std.yaml +++ b/tests/std.yaml @@ -1,28 +1,28 @@ tests: - name: condition_type_guards program: (a (q 2 (q 2 (i (= (f (q 1)) (q . 1)) (q 2 (i (= (f (q 1 . 42)) (q . 1)) (q 2 (i (= (f (c (q . 43) (c 5 (q ())))) (q . 43)) (q 2 (i (= (f (c (q . 44) (c 5 (q ())))) (q . 44)) (q 2 (i (= (f (c (q . 45) (c 5 (q ())))) (q . 45)) (q 2 (i (= (f (c (q . 46) (c 5 (q ())))) (q . 46)) (q 2 (i (= (f (c (q . 47) (c 5 (q ())))) (q . 47)) (q 2 (i (= (f (c (q . 48) (c 5 (q ())))) (q . 48)) (q 2 (i (= (f (c (q . 49) (c 5 (q ())))) (q . 49)) (q 2 (i (= (f (c (q . 50) (c 5 (q ())))) (q . 50)) (q 2 (i (= (f (c (q . 51) (c 4 (q 42)))) (q . 51)) (q 2 (i (= (f (q 52 42)) (q . 52)) (q 2 (i (= (f (q 60 ())) (q . 60)) (q 2 (i (= (f (c (q . 61) (c 10 ()))) (q . 61)) (q 2 (i (= (f (q 62 ())) (q . 62)) (q 2 (i (= (f (c (q . 63) (c 10 ()))) (q . 63)) (q 2 (i (= (f (c (q . 64) (c 14 ()))) (q . 64)) (q 2 (i (= (f (c (q . 65) (c 4 ()))) (q . 65)) (q 2 (i (= (f (q 66 42 () 42)) (q . 66)) (q 2 (i (= (f (q 67 42 () 42)) (q . 67)) (q 2 (i (= (f (c (q . 70) (c 14 ()))) (q . 70)) (q 2 (i (= (f (c (q . 71) (c 10 ()))) (q . 71)) (q 2 (i (= (f (c (q . 72) (c 4 ()))) (q . 72)) (q 2 (i (= (f (q 73 42)) (q . 73)) (q 2 (i (= (f (q 74 42)) (q . 74)) (q 2 (i (= (f (q 75 42)) (q . 75)) (q 2 (i (= (f (q 76)) (q . 76)) (q 2 (i (= (f (q 80 42)) (q . 80)) (q 2 (i (= (f (q 81 42)) (q . 81)) (q 2 (i (= (f (q 82 42)) (q . 82)) (q 2 (i (= (f (q 83 42)) (q . 83)) (q 2 (i (= (f (q 84 42)) (q . 84)) (q 2 (i (= (f (q 85 42)) (q . 85)) (q 2 (i (= (f (q 86 42)) (q . 86)) (q 2 (i (= (f (q 87 42)) (q . 87)) (q 2 (i (= (f (q 90 42)) (q . 90)) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (c (sha256 (q . "puzzle_hash")) (c (sha256 (q . 26980)) (sha256 (q . "coin_id")))) 1)) (c (point_add) ())) - debug_program: (a (q 2 (q 2 (i (= (f (c (q . 1) ())) (q . 1)) (q 2 (i (= (f (c (q . 1) (q . 42))) (q . 1)) (q 2 (i (= (f (c (q . 43) (c 5 (c () ())))) (q . 43)) (q 2 (i (= (f (c (q . 44) (c 5 (c () ())))) (q . 44)) (q 2 (i (= (f (c (q . 45) (c 5 (c () ())))) (q . 45)) (q 2 (i (= (f (c (q . 46) (c 5 (c () ())))) (q . 46)) (q 2 (i (= (f (c (q . 47) (c 5 (c () ())))) (q . 47)) (q 2 (i (= (f (c (q . 48) (c 5 (c () ())))) (q . 48)) (q 2 (i (= (f (c (q . 49) (c 5 (c () ())))) (q . 49)) (q 2 (i (= (f (c (q . 50) (c 5 (c () ())))) (q . 50)) (q 2 (i (= (f (c (q . 51) (c 4 (c (q . 42) ())))) (q . 51)) (q 2 (i (= (f (c (q . 52) (c (q . 42) ()))) (q . 52)) (q 2 (i (= (f (c (q . 60) (c () ()))) (q . 60)) (q 2 (i (= (f (c (q . 61) (c 10 ()))) (q . 61)) (q 2 (i (= (f (c (q . 62) (c () ()))) (q . 62)) (q 2 (i (= (f (c (q . 63) (c 10 ()))) (q . 63)) (q 2 (i (= (f (c (q . 64) (c 14 ()))) (q . 64)) (q 2 (i (= (f (c (q . 65) (c 4 ()))) (q . 65)) (q 2 (i (= (f (c (q . 66) (c (q . 42) (c () (c (q . 42) ()))))) (q . 66)) (q 2 (i (= (f (c (q . 67) (c (q . 42) (c () (c (q . 42) ()))))) (q . 67)) (q 2 (i (= (f (c (q . 70) (c 14 ()))) (q . 70)) (q 2 (i (= (f (c (q . 71) (c 10 ()))) (q . 71)) (q 2 (i (= (f (c (q . 72) (c 4 ()))) (q . 72)) (q 2 (i (= (f (c (q . 73) (c (q . 42) ()))) (q . 73)) (q 2 (i (= (f (c (q . 74) (c (q . 42) ()))) (q . 74)) (q 2 (i (= (f (c (q . 75) (c (q . 42) ()))) (q . 75)) (q 2 (i (= (f (c (q . 76) ())) (q . 76)) (q 2 (i (= (f (c (q . 80) (c (q . 42) ()))) (q . 80)) (q 2 (i (= (f (c (q . 81) (c (q . 42) ()))) (q . 81)) (q 2 (i (= (f (c (q . 82) (c (q . 42) ()))) (q . 82)) (q 2 (i (= (f (c (q . 83) (c (q . 42) ()))) (q . 83)) (q 2 (i (= (f (c (q . 84) (c (q . 42) ()))) (q . 84)) (q 2 (i (= (f (c (q . 85) (c (q . 42) ()))) (q . 85)) (q 2 (i (= (f (c (q . 86) (c (q . 42) ()))) (q . 86)) (q 2 (i (= (f (c (q . 87) (c (q . 42) ()))) (q . 87)) (q 2 (i (= (f (c (q . 90) (c (q . 42) ()))) (q . 90)) (q) (q 8 (q . "assertion failed at std.rue:41:5"))) 1) (q 8 (q . "assertion failed at std.rue:40:5"))) 1) (q 8 (q . "assertion failed at std.rue:39:5"))) 1) (q 8 (q . "assertion failed at std.rue:38:5"))) 1) (q 8 (q . "assertion failed at std.rue:37:5"))) 1) (q 8 (q . "assertion failed at std.rue:36:5"))) 1) (q 8 (q . "assertion failed at std.rue:35:5"))) 1) (q 8 (q . "assertion failed at std.rue:34:5"))) 1) (q 8 (q . "assertion failed at std.rue:33:5"))) 1) (q 8 (q . "assertion failed at std.rue:32:5"))) 1) (q 8 (q . "assertion failed at std.rue:31:5"))) 1) (q 8 (q . "assertion failed at std.rue:30:5"))) 1) (q 8 (q . "assertion failed at std.rue:29:5"))) 1) (q 8 (q . "assertion failed at std.rue:28:5"))) 1) (q 8 (q . "assertion failed at std.rue:27:5"))) 1) (q 8 (q . "assertion failed at std.rue:26:5"))) 1) (q 8 (q . "assertion failed at std.rue:25:5"))) 1) (q 8 (q . "assertion failed at std.rue:24:5"))) 1) (q 8 (q . "assertion failed at std.rue:23:5"))) 1) (q 8 (q . "assertion failed at std.rue:22:5"))) 1) (q 8 (q . "assertion failed at std.rue:21:5"))) 1) (q 8 (q . "assertion failed at std.rue:20:5"))) 1) (q 8 (q . "assertion failed at std.rue:19:5"))) 1) (q 8 (q . "assertion failed at std.rue:18:5"))) 1) (q 8 (q . "assertion failed at std.rue:17:5"))) 1) (q 8 (q . "assertion failed at std.rue:16:5"))) 1) (q 8 (q . "assertion failed at std.rue:15:5"))) 1) (q 8 (q . "assertion failed at std.rue:14:5"))) 1) (q 8 (q . "assertion failed at std.rue:13:5"))) 1) (q 8 (q . "assertion failed at std.rue:12:5"))) 1) (q 8 (q . "assertion failed at std.rue:11:5"))) 1) (q 8 (q . "assertion failed at std.rue:10:5"))) 1) (q 8 (q . "assertion failed at std.rue:9:5"))) 1) (q 8 (q . "assertion failed at std.rue:8:5"))) 1) (q 8 (q . "assertion failed at std.rue:7:5"))) 1) (q 8 (q . "assertion failed at std.rue:6:5"))) 1) (c (c (sha256 (q . "puzzle_hash")) (c (sha256 (q . 26980)) (sha256 (q . "coin_id")))) 1)) (c (point_add) ())) + debug_program: (a (q 2 (q 2 (i (= (f (c (q . 1) ())) (q . 1)) (q 2 (i (= (f (c (q . 1) (q . 42))) (q . 1)) (q 2 (i (= (f (c (q . 43) (c 5 (c () ())))) (q . 43)) (q 2 (i (= (f (c (q . 44) (c 5 (c () ())))) (q . 44)) (q 2 (i (= (f (c (q . 45) (c 5 (c () ())))) (q . 45)) (q 2 (i (= (f (c (q . 46) (c 5 (c () ())))) (q . 46)) (q 2 (i (= (f (c (q . 47) (c 5 (c () ())))) (q . 47)) (q 2 (i (= (f (c (q . 48) (c 5 (c () ())))) (q . 48)) (q 2 (i (= (f (c (q . 49) (c 5 (c () ())))) (q . 49)) (q 2 (i (= (f (c (q . 50) (c 5 (c () ())))) (q . 50)) (q 2 (i (= (f (c (q . 51) (c 4 (c (q . 42) ())))) (q . 51)) (q 2 (i (= (f (c (q . 52) (c (q . 42) ()))) (q . 52)) (q 2 (i (= (f (c (q . 60) (c () ()))) (q . 60)) (q 2 (i (= (f (c (q . 61) (c 10 ()))) (q . 61)) (q 2 (i (= (f (c (q . 62) (c () ()))) (q . 62)) (q 2 (i (= (f (c (q . 63) (c 10 ()))) (q . 63)) (q 2 (i (= (f (c (q . 64) (c 14 ()))) (q . 64)) (q 2 (i (= (f (c (q . 65) (c 4 ()))) (q . 65)) (q 2 (i (= (f (c (q . 66) (c (q . 42) (c () (c (q . 42) ()))))) (q . 66)) (q 2 (i (= (f (c (q . 67) (c (q . 42) (c () (c (q . 42) ()))))) (q . 67)) (q 2 (i (= (f (c (q . 70) (c 14 ()))) (q . 70)) (q 2 (i (= (f (c (q . 71) (c 10 ()))) (q . 71)) (q 2 (i (= (f (c (q . 72) (c 4 ()))) (q . 72)) (q 2 (i (= (f (c (q . 73) (c (q . 42) ()))) (q . 73)) (q 2 (i (= (f (c (q . 74) (c (q . 42) ()))) (q . 74)) (q 2 (i (= (f (c (q . 75) (c (q . 42) ()))) (q . 75)) (q 2 (i (= (f (c (q . 76) ())) (q . 76)) (q 2 (i (= (f (c (q . 80) (c (q . 42) ()))) (q . 80)) (q 2 (i (= (f (c (q . 81) (c (q . 42) ()))) (q . 81)) (q 2 (i (= (f (c (q . 82) (c (q . 42) ()))) (q . 82)) (q 2 (i (= (f (c (q . 83) (c (q . 42) ()))) (q . 83)) (q 2 (i (= (f (c (q . 84) (c (q . 42) ()))) (q . 84)) (q 2 (i (= (f (c (q . 85) (c (q . 42) ()))) (q . 85)) (q 2 (i (= (f (c (q . 86) (c (q . 42) ()))) (q . 86)) (q 2 (i (= (f (c (q . 87) (c (q . 42) ()))) (q . 87)) (q 2 (i (= (f (c (q . 90) (c (q . 42) ()))) (q . 90)) (q) (q 8 (q . "assertion failed at std.rue:52:5"))) 1) (q 8 (q . "assertion failed at std.rue:51:5"))) 1) (q 8 (q . "assertion failed at std.rue:50:5"))) 1) (q 8 (q . "assertion failed at std.rue:49:5"))) 1) (q 8 (q . "assertion failed at std.rue:48:5"))) 1) (q 8 (q . "assertion failed at std.rue:47:5"))) 1) (q 8 (q . "assertion failed at std.rue:46:5"))) 1) (q 8 (q . "assertion failed at std.rue:45:5"))) 1) (q 8 (q . "assertion failed at std.rue:44:5"))) 1) (q 8 (q . "assertion failed at std.rue:43:5"))) 1) (q 8 (q . "assertion failed at std.rue:42:5"))) 1) (q 8 (q . "assertion failed at std.rue:41:5"))) 1) (q 8 (q . "assertion failed at std.rue:40:5"))) 1) (q 8 (q . "assertion failed at std.rue:39:5"))) 1) (q 8 (q . "assertion failed at std.rue:38:5"))) 1) (q 8 (q . "assertion failed at std.rue:37:5"))) 1) (q 8 (q . "assertion failed at std.rue:36:5"))) 1) (q 8 (q . "assertion failed at std.rue:35:5"))) 1) (q 8 (q . "assertion failed at std.rue:32:5"))) 1) (q 8 (q . "assertion failed at std.rue:31:5"))) 1) (q 8 (q . "assertion failed at std.rue:30:5"))) 1) (q 8 (q . "assertion failed at std.rue:29:5"))) 1) (q 8 (q . "assertion failed at std.rue:28:5"))) 1) (q 8 (q . "assertion failed at std.rue:27:5"))) 1) (q 8 (q . "assertion failed at std.rue:26:5"))) 1) (q 8 (q . "assertion failed at std.rue:25:5"))) 1) (q 8 (q . "assertion failed at std.rue:24:5"))) 1) (q 8 (q . "assertion failed at std.rue:23:5"))) 1) (q 8 (q . "assertion failed at std.rue:19:5"))) 1) (q 8 (q . "assertion failed at std.rue:15:5"))) 1) (q 8 (q . "assertion failed at std.rue:11:5"))) 1) (q 8 (q . "assertion failed at std.rue:10:5"))) 1) (q 8 (q . "assertion failed at std.rue:9:5"))) 1) (q 8 (q . "assertion failed at std.rue:8:5"))) 1) (q 8 (q . "assertion failed at std.rue:7:5"))) 1) (q 8 (q . "assertion failed at std.rue:6:5"))) 1) (c (c (sha256 (q . "puzzle_hash")) (c (sha256 (q . 26980)) (sha256 (q . "coin_id")))) 1)) (c (point_add) ())) output: () runtime_cost: 121231 byte_cost: 19380000 total_cost: 19501231 - name: hashing program: (a (q 2 (i (= (a 2 (c 2 (q . "hello"))) (q . 0xcceeb7a985ecc3dabcb4c8f666cd637f16f008e3c963db6aa6f83a7b288c54ef)) (q 2 (i (= (a 2 (c 2 (q . "hello"))) (sha256 (q . 0x0168656c6c6f))) (q 2 (i (= (sha256 (q . 0x0168656c6c6f)) (sha256 (q . 0x0168656c6c6f))) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) ())) - debug_program: (a (q 2 (i (= (a 2 (c 2 (q . "hello"))) (q . 0xcceeb7a985ecc3dabcb4c8f666cd637f16f008e3c963db6aa6f83a7b288c54ef)) (q 2 (i (= (a 2 (c 2 (q . "hello"))) (sha256 (concat (q . 1) (q . "hello")))) (q 2 (i (= (sha256 (concat (q . 1) (q . "hello"))) (sha256 (concat (q . 1) (q . "hello")))) (q) (q 8 (q . "assertion failed at std.rue:47:5"))) 1) (q 8 (q . "assertion failed at std.rue:46:5"))) 1) (q 8 (q . "assertion failed at std.rue:45:5"))) 1) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) ())) + debug_program: (a (q 2 (i (= (a 2 (c 2 (q . "hello"))) (q . 0xcceeb7a985ecc3dabcb4c8f666cd637f16f008e3c963db6aa6f83a7b288c54ef)) (q 2 (i (= (a 2 (c 2 (q . "hello"))) (sha256 (concat (q . 1) (q . "hello")))) (q 2 (i (= (sha256 (concat (q . 1) (q . "hello"))) (sha256 (concat (q . 1) (q . "hello")))) (q) (q 8 (q . "assertion failed at std.rue:58:5"))) 1) (q 8 (q . "assertion failed at std.rue:57:5"))) 1) (q 8 (q . "assertion failed at std.rue:56:5"))) 1) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) ())) output: () runtime_cost: 5767 byte_cost: 3204000 total_cost: 3209767 - name: currying program: (a (q 2 (i (= (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) (a 4 (c 4 (q . "xyz")))) (sha256 (q . 2) (a 6 (c 6 (c (a 4 (c 4 (q . 97))) (c (a 4 (c 4 (q . 98))) (c (a 4 (c 4 (q . 99))) ()))))) (sha256 (q . 1))))) (q . 0x932daac5826a521478ec3dc75c49f889278bc56bf6afa45cdfa3aadffe2e76ed)) (q) (q 8)) 1) (c (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (q 2 (i 3 (q 11 (q . 2) (sha256 (q . 260)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 5) (sha256 (q . 2) (a 2 (c 2 7)) (sha256 (q . 1))))) (q 11 (q . 257))) 1)) ())) - debug_program: (a (q 2 (q 2 (i (= 2 (q . 0x932daac5826a521478ec3dc75c49f889278bc56bf6afa45cdfa3aadffe2e76ed)) (q) (q 8 (q . "assertion failed at std.rue:52:5"))) 1) (c (a 22 (c (c 10 (c 46 62)) (c (a 4 (c 4 (q . "xyz"))) (c (a 4 (c 4 (q . 97))) (c (a 4 (c 4 (q . 98))) (c (a 4 (c 4 (q . 99))) ())))))) 1)) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) ())) + debug_program: (a (q 2 (q 2 (i (= 2 (q . 0x932daac5826a521478ec3dc75c49f889278bc56bf6afa45cdfa3aadffe2e76ed)) (q) (q 8 (q . "assertion failed at std.rue:63:5"))) 1) (c (a 22 (c (c 10 (c 46 62)) (c (a 4 (c 4 (q . "xyz"))) (c (a 4 (c 4 (q . 97))) (c (a 4 (c 4 (q . 98))) (c (a 4 (c 4 (q . 99))) ())))))) 1)) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) ())) output: () runtime_cost: 31127 byte_cost: 4800000 total_cost: 4831127 - name: recursion program: (a (q 3 () (a 10 (c 10 (c 4 (c 14 (c (c () (c (q . "Hello, world!") (c (q 100 . 200) (c (q 1 2 3) (c (q 16 2 5) (c (c (q . 2) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 14) (c (q . 1) ()))) ()))) ()))) ()))) ())))))) ()))))) ()) (c (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (c (q 2 (i 23 (q 2 (q 2 (i (a 23 (c 23 (c 4 4))) (q 2 (i (= (a 11 (c 11 4)) (a 11 (c 11 4))) (q 3 () (a 5 (c 5 (c 11 (c 23 (c 6 ()))))) ()) (q 8)) 1) (q 8)) 1) (c 23 1)) (q)) 1) (q 2 (i (l 7) (q 2 (i (l 5) (q 2 (i (a 2 (c 2 (c 9 11))) (q 2 (i (a 2 (c 2 (c 13 15))) (q 1 . 1) (q)) 1) (q)) 1) (q)) 1) (q 2 (i (l 5) (q) (q 9 7 5)) 1)) 1))) ())) - debug_program: (a (q 3 (q . 1) () (a 10 (c 10 (c 4 (c 14 (c (c () (c (q . "Hello, world!") (c (c (q . 100) (q . 200)) (c (c (q . 1) (c (q . 2) (c (q . 3) ()))) (c (c (q . 2) (c (c (q . 1) (q 16 2 5)) (c (q . 1) ()))) (c (c (q . 2) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 14) (c (q . 1) ()))) ()))) ()))) ()))) ())))))) ())))))) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (c (q 2 (i (not (l 23)) (q) (q 2 (q 2 (i (a 23 (c 23 (c (f 2) (f 2)))) (q 2 (i (= (a 11 (c 11 (f 2))) (a 11 (c 11 (f 2)))) (q 3 (q . 1) () (a 5 (c 5 (c 11 (c 23 (c (r 2) ())))))) (q 8 (q . "assertion failed at std.rue:74:5"))) 1) (q 8 (q . "assertion failed at std.rue:73:5"))) 1) (c 23 1))) 1) (q 2 (i (not (l 7)) (q 2 (i (not (l 5)) (q 9 7 5) (q)) 1) (q 2 (i (not (l 5)) (q) (q 2 (i (a 2 (c 2 (c (f 5) (f 7)))) (q 2 (i (a 2 (c 2 (c (r 5) (r 7)))) (q 1 . 1) (q)) 1) (q)) 1)) 1)) 1))) ())) + debug_program: (a (q 3 (q . 1) () (a 10 (c 10 (c 4 (c 14 (c (c () (c (q . "Hello, world!") (c (c (q . 100) (q . 200)) (c (c (q . 1) (c (q . 2) (c (q . 3) ()))) (c (c (q . 2) (c (c (q . 1) (q 16 2 5)) (c (q . 1) ()))) (c (c (q . 2) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 14) (c (q . 1) ()))) ()))) ()))) ()))) ())))))) ())))))) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (c (q 2 (i (not (l 23)) (q) (q 2 (q 2 (i (a 23 (c 23 (c (f 2) (f 2)))) (q 2 (i (= (a 11 (c 11 (f 2))) (a 11 (c 11 (f 2)))) (q 3 (q . 1) () (a 5 (c 5 (c 11 (c 23 (c (r 2) ())))))) (q 8 (q . "assertion failed at std.rue:80:5"))) 1) (q 8 (q . "assertion failed at std.rue:79:5"))) 1) (c 23 1))) 1) (q 2 (i (not (l 7)) (q 2 (i (not (l 5)) (q 9 7 5) (q)) 1) (q 2 (i (not (l 5)) (q) (q 2 (i (a 2 (c 2 (c (f 5) (f 7)))) (q 2 (i (a 2 (c 2 (c (r 5) (r 7)))) (q 1 . 1) (q)) 1) (q)) 1)) 1)) 1))) ())) output: () runtime_cost: 2402920 byte_cost: 7512000 diff --git a/tests/stress/many_params_binary_tree.rue b/tests/stress/many_params_binary_tree.rue index 83fca6b6..8ef756a3 100644 --- a/tests/stress/many_params_binary_tree.rue +++ b/tests/stress/many_params_binary_tree.rue @@ -1,5 +1,266 @@ test fn call_binary_tree() { - function(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260); + function( + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260 + ); } fn function( @@ -264,14 +525,284 @@ fn function( y_10: Int, z_10: Int, ) { - assert (a_1 + b_1 + c_1 + d_1 + e_1 + f_1 + g_1 + h_1 + i_1 + j_1 + k_1 + l_1 + m_1 + n_1 + o_1 + p_1 + q_1 + r_1 + s_1 + t_1 + u_1 + v_1 + w_1 + x_1 + y_1 + z_1) > 0; - assert (a_2 + b_2 + c_2 + d_2 + e_2 + f_2 + g_2 + h_2 + i_2 + j_2 + k_2 + l_2 + m_2 + n_2 + o_2 + p_2 + q_2 + r_2 + s_2 + t_2 + u_2 + v_2 + w_2 + x_2 + y_2 + z_2) > 0; - assert (a_3 + b_3 + c_3 + d_3 + e_3 + f_3 + g_3 + h_3 + i_3 + j_3 + k_3 + l_3 + m_3 + n_3 + o_3 + p_3 + q_3 + r_3 + s_3 + t_3 + u_3 + v_3 + w_3 + x_3 + y_3 + z_3) > 0; - assert (a_4 + b_4 + c_4 + d_4 + e_4 + f_4 + g_4 + h_4 + i_4 + j_4 + k_4 + l_4 + m_4 + n_4 + o_4 + p_4 + q_4 + r_4 + s_4 + t_4 + u_4 + v_4 + w_4 + x_4 + y_4 + z_4) > 0; - assert (a_5 + b_5 + c_5 + d_5 + e_5 + f_5 + g_5 + h_5 + i_5 + j_5 + k_5 + l_5 + m_5 + n_5 + o_5 + p_5 + q_5 + r_5 + s_5 + t_5 + u_5 + v_5 + w_5 + x_5 + y_5 + z_5) > 0; - assert (a_6 + b_6 + c_6 + d_6 + e_6 + f_6 + g_6 + h_6 + i_6 + j_6 + k_6 + l_6 + m_6 + n_6 + o_6 + p_6 + q_6 + r_6 + s_6 + t_6 + u_6 + v_6 + w_6 + x_6 + y_6 + z_6) > 0; - assert (a_7 + b_7 + c_7 + d_7 + e_7 + f_7 + g_7 + h_7 + i_7 + j_7 + k_7 + l_7 + m_7 + n_7 + o_7 + p_7 + q_7 + r_7 + s_7 + t_7 + u_7 + v_7 + w_7 + x_7 + y_7 + z_7) > 0; - assert (a_8 + b_8 + c_8 + d_8 + e_8 + f_8 + g_8 + h_8 + i_8 + j_8 + k_8 + l_8 + m_8 + n_8 + o_8 + p_8 + q_8 + r_8 + s_8 + t_8 + u_8 + v_8 + w_8 + x_8 + y_8 + z_8) > 0; - assert (a_9 + b_9 + c_9 + d_9 + e_9 + f_9 + g_9 + h_9 + i_9 + j_9 + k_9 + l_9 + m_9 + n_9 + o_9 + p_9 + q_9 + r_9 + s_9 + t_9 + u_9 + v_9 + w_9 + x_9 + y_9 + z_9) > 0; - assert (a_10 + b_10 + c_10 + d_10 + e_10 + f_10 + g_10 + h_10 + i_10 + j_10 + k_10 + l_10 + m_10 + n_10 + o_10 + p_10 + q_10 + r_10 + s_10 + t_10 + u_10 + v_10 + w_10 + x_10 + y_10 + z_10) > 0; + assert ( + a_1 + + b_1 + + c_1 + + d_1 + + e_1 + + f_1 + + g_1 + + h_1 + + i_1 + + j_1 + + k_1 + + l_1 + + m_1 + + n_1 + + o_1 + + p_1 + + q_1 + + r_1 + + s_1 + + t_1 + + u_1 + + v_1 + + w_1 + + x_1 + + y_1 + + z_1 + ) > 0; + assert ( + a_2 + + b_2 + + c_2 + + d_2 + + e_2 + + f_2 + + g_2 + + h_2 + + i_2 + + j_2 + + k_2 + + l_2 + + m_2 + + n_2 + + o_2 + + p_2 + + q_2 + + r_2 + + s_2 + + t_2 + + u_2 + + v_2 + + w_2 + + x_2 + + y_2 + + z_2 + ) > 0; + assert ( + a_3 + + b_3 + + c_3 + + d_3 + + e_3 + + f_3 + + g_3 + + h_3 + + i_3 + + j_3 + + k_3 + + l_3 + + m_3 + + n_3 + + o_3 + + p_3 + + q_3 + + r_3 + + s_3 + + t_3 + + u_3 + + v_3 + + w_3 + + x_3 + + y_3 + + z_3 + ) > 0; + assert ( + a_4 + + b_4 + + c_4 + + d_4 + + e_4 + + f_4 + + g_4 + + h_4 + + i_4 + + j_4 + + k_4 + + l_4 + + m_4 + + n_4 + + o_4 + + p_4 + + q_4 + + r_4 + + s_4 + + t_4 + + u_4 + + v_4 + + w_4 + + x_4 + + y_4 + + z_4 + ) > 0; + assert ( + a_5 + + b_5 + + c_5 + + d_5 + + e_5 + + f_5 + + g_5 + + h_5 + + i_5 + + j_5 + + k_5 + + l_5 + + m_5 + + n_5 + + o_5 + + p_5 + + q_5 + + r_5 + + s_5 + + t_5 + + u_5 + + v_5 + + w_5 + + x_5 + + y_5 + + z_5 + ) > 0; + assert ( + a_6 + + b_6 + + c_6 + + d_6 + + e_6 + + f_6 + + g_6 + + h_6 + + i_6 + + j_6 + + k_6 + + l_6 + + m_6 + + n_6 + + o_6 + + p_6 + + q_6 + + r_6 + + s_6 + + t_6 + + u_6 + + v_6 + + w_6 + + x_6 + + y_6 + + z_6 + ) > 0; + assert ( + a_7 + + b_7 + + c_7 + + d_7 + + e_7 + + f_7 + + g_7 + + h_7 + + i_7 + + j_7 + + k_7 + + l_7 + + m_7 + + n_7 + + o_7 + + p_7 + + q_7 + + r_7 + + s_7 + + t_7 + + u_7 + + v_7 + + w_7 + + x_7 + + y_7 + + z_7 + ) > 0; + assert ( + a_8 + + b_8 + + c_8 + + d_8 + + e_8 + + f_8 + + g_8 + + h_8 + + i_8 + + j_8 + + k_8 + + l_8 + + m_8 + + n_8 + + o_8 + + p_8 + + q_8 + + r_8 + + s_8 + + t_8 + + u_8 + + v_8 + + w_8 + + x_8 + + y_8 + + z_8 + ) > 0; + assert ( + a_9 + + b_9 + + c_9 + + d_9 + + e_9 + + f_9 + + g_9 + + h_9 + + i_9 + + j_9 + + k_9 + + l_9 + + m_9 + + n_9 + + o_9 + + p_9 + + q_9 + + r_9 + + s_9 + + t_9 + + u_9 + + v_9 + + w_9 + + x_9 + + y_9 + + z_9 + ) > 0; + assert ( + a_10 + + b_10 + + c_10 + + d_10 + + e_10 + + f_10 + + g_10 + + h_10 + + i_10 + + j_10 + + k_10 + + l_10 + + m_10 + + n_10 + + o_10 + + p_10 + + q_10 + + r_10 + + s_10 + + t_10 + + u_10 + + v_10 + + w_10 + + x_10 + + y_10 + + z_10 + ) > 0; } diff --git a/tests/stress/many_params_binary_tree.yaml b/tests/stress/many_params_binary_tree.yaml index 1c1b5bf7..b49493cb 100644 --- a/tests/stress/many_params_binary_tree.yaml +++ b/tests/stress/many_params_binary_tree.yaml @@ -1,7 +1,7 @@ tests: - name: call_binary_tree program: (i () (a (i (> (q . 351) ()) (q 2 (i (> (q . 1027) ()) (q 2 (i (> (q . 1703) ()) (q 2 (i (> (q . 2379) ()) (q 2 (i (> (q . 3055) ()) (q 2 (i (> (q . 3731) ()) (q 2 (i (> (q . 4407) ()) (q 2 (i (> (q . 5083) ()) (q 2 (i (> (q . 5759) ()) (q 2 (i (> (q . 6435) ()) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) ()) - debug_program: (a (q 3 (q . 1) () (a 2 (c (c (c (c (c (c (c (c (q . 1) (q . 2)) (c (q . 3) (q . 4))) (c (c (q . 5) (q . 6)) (c (q . 7) (q . 8)))) (c (c (c (q . 9) (q . 10)) (c (q . 11) (q . 12))) (c (c (q . 13) (q . 14)) (c (q . 15) (q . 16))))) (c (c (c (c (q . 17) (q . 18)) (c (q . 19) (q . 20))) (c (c (q . 21) (q . 22)) (c (q . 23) (q . 24)))) (c (c (c (q . 25) (q . 26)) (c (q . 27) (q . 28))) (c (c (q . 29) (q . 30)) (c (q . 31) (q . 32)))))) (c (c (c (c (c (q . 33) (q . 34)) (c (q . 35) (q . 36))) (c (c (q . 37) (q . 38)) (c (q . 39) (q . 40)))) (c (c (c (q . 41) (q . 42)) (c (q . 43) (q . 44))) (c (c (q . 45) (q . 46)) (c (q . 47) (q . 48))))) (c (c (c (c (q . 49) (q . 50)) (c (q . 51) (q . 52))) (c (c (q . 53) (q . 54)) (c (q . 55) (q . 56)))) (c (c (c (q . 57) (q . 58)) (c (q . 59) (q . 60))) (c (c (q . 61) (q . 62)) (c (q . 63) (c (q . 64) (q . 65)))))))) (c (c (c (c (c (c (q . 66) (q . 67)) (c (q . 68) (q . 69))) (c (c (q . 70) (q . 71)) (c (q . 72) (q . 73)))) (c (c (c (q . 74) (q . 75)) (c (q . 76) (q . 77))) (c (c (q . 78) (q . 79)) (c (q . 80) (q . 81))))) (c (c (c (c (q . 82) (q . 83)) (c (q . 84) (q . 85))) (c (c (q . 86) (q . 87)) (c (q . 88) (q . 89)))) (c (c (c (q . 90) (q . 91)) (c (q . 92) (q . 93))) (c (c (q . 94) (q . 95)) (c (q . 96) (q . 97)))))) (c (c (c (c (c (q . 98) (q . 99)) (c (q . 100) (q . 101))) (c (c (q . 102) (q . 103)) (c (q . 104) (q . 105)))) (c (c (c (q . 106) (q . 107)) (c (q . 108) (q . 109))) (c (c (q . 110) (q . 111)) (c (q . 112) (q . 113))))) (c (c (c (c (q . 114) (q . 115)) (c (q . 116) (q . 117))) (c (c (q . 118) (q . 119)) (c (q . 120) (q . 121)))) (c (c (c (q . 122) (q . 123)) (c (q . 124) (q . 125))) (c (c (q . 126) (q . 127)) (c (q . 128) (c (q . 129) (q . 130))))))))) (c (c (c (c (c (c (c (q . 131) (q . 132)) (c (q . 133) (q . 134))) (c (c (q . 135) (q . 136)) (c (q . 137) (q . 138)))) (c (c (c (q . 139) (q . 140)) (c (q . 141) (q . 142))) (c (c (q . 143) (q . 144)) (c (q . 145) (q . 146))))) (c (c (c (c (q . 147) (q . 148)) (c (q . 149) (q . 150))) (c (c (q . 151) (q . 152)) (c (q . 153) (q . 154)))) (c (c (c (q . 155) (q . 156)) (c (q . 157) (q . 158))) (c (c (q . 159) (q . 160)) (c (q . 161) (q . 162)))))) (c (c (c (c (c (q . 163) (q . 164)) (c (q . 165) (q . 166))) (c (c (q . 167) (q . 168)) (c (q . 169) (q . 170)))) (c (c (c (q . 171) (q . 172)) (c (q . 173) (q . 174))) (c (c (q . 175) (q . 176)) (c (q . 177) (q . 178))))) (c (c (c (c (q . 179) (q . 180)) (c (q . 181) (q . 182))) (c (c (q . 183) (q . 184)) (c (q . 185) (q . 186)))) (c (c (c (q . 187) (q . 188)) (c (q . 189) (q . 190))) (c (c (q . 191) (q . 192)) (c (q . 193) (c (q . 194) (q . 195)))))))) (c (c (c (c (c (c (q . 196) (q . 197)) (c (q . 198) (q . 199))) (c (c (q . 200) (q . 201)) (c (q . 202) (q . 203)))) (c (c (c (q . 204) (q . 205)) (c (q . 206) (q . 207))) (c (c (q . 208) (q . 209)) (c (q . 210) (q . 211))))) (c (c (c (c (q . 212) (q . 213)) (c (q . 214) (q . 215))) (c (c (q . 216) (q . 217)) (c (q . 218) (q . 219)))) (c (c (c (q . 220) (q . 221)) (c (q . 222) (q . 223))) (c (c (q . 224) (q . 225)) (c (q . 226) (q . 227)))))) (c (c (c (c (c (q . 228) (q . 229)) (c (q . 230) (q . 231))) (c (c (q . 232) (q . 233)) (c (q . 234) (q . 235)))) (c (c (c (q . 236) (q . 237)) (c (q . 238) (q . 239))) (c (c (q . 240) (q . 241)) (c (q . 242) (q . 243))))) (c (c (c (c (q . 244) (q . 245)) (c (q . 246) (q . 247))) (c (c (q . 248) (q . 249)) (c (q . 250) (q . 251)))) (c (c (c (q . 252) (q . 253)) (c (q . 254) (q . 255))) (c (c (q . 256) (q . 257)) (c (q . 258) (c (q . 259) (q . 260)))))))))))) (c (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 256 384) 320) 448) 288) 416) 352) 480) 272) 400) 336) 464) 304) 432) 368) 496) 264) 392) 328) 456) 296) 424) 360) 488) 280) 408) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 344 472) 312) 440) 376) 504) 260) 388) 324) 452) 292) 420) 356) 484) 276) 404) 340) 468) 308) 436) 372) 500) 268) 396) 332) 460) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 300 428) 364) 492) 284) 412) 348) 476) 316) 444) 380) 764) 1020) 258) 386) 322) 450) 290) 418) 354) 482) 274) 402) 338) 466) 306) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 434 370) 498) 266) 394) 330) 458) 298) 426) 362) 490) 282) 410) 346) 474) 314) 442) 378) 506) 262) 390) 326) 454) 294) 422) 358) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 486 278) 406) 342) 470) 310) 438) 374) 502) 270) 398) 334) 462) 302) 430) 366) 494) 286) 414) 350) 478) 318) 446) 382) 766) 1022) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 257 385) 321) 449) 289) 417) 353) 481) 273) 401) 337) 465) 305) 433) 369) 497) 265) 393) 329) 457) 297) 425) 361) 489) 281) 409) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 345 473) 313) 441) 377) 505) 261) 389) 325) 453) 293) 421) 357) 485) 277) 405) 341) 469) 309) 437) 373) 501) 269) 397) 333) 461) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 301 429) 365) 493) 285) 413) 349) 477) 317) 445) 381) 765) 1021) 259) 387) 323) 451) 291) 419) 355) 483) 275) 403) 339) 467) 307) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 435 371) 499) 267) 395) 331) 459) 299) 427) 363) 491) 283) 411) 347) 475) 315) 443) 379) 507) 263) 391) 327) 455) 295) 423) 359) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 487 279) 407) 343) 471) 311) 439) 375) 503) 271) 399) 335) 463) 303) 431) 367) 495) 287) 415) 351) 479) 319) 447) 383) 767) 1023) ()) (q) (q 8 (q . "assertion failed at many_params_binary_tree.rue:276:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:275:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:274:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:273:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:272:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:271:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:270:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:269:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:268:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:267:5"))) 1) ())) + debug_program: (a (q 3 (q . 1) () (a 2 (c (c (c (c (c (c (c (c (q . 1) (q . 2)) (c (q . 3) (q . 4))) (c (c (q . 5) (q . 6)) (c (q . 7) (q . 8)))) (c (c (c (q . 9) (q . 10)) (c (q . 11) (q . 12))) (c (c (q . 13) (q . 14)) (c (q . 15) (q . 16))))) (c (c (c (c (q . 17) (q . 18)) (c (q . 19) (q . 20))) (c (c (q . 21) (q . 22)) (c (q . 23) (q . 24)))) (c (c (c (q . 25) (q . 26)) (c (q . 27) (q . 28))) (c (c (q . 29) (q . 30)) (c (q . 31) (q . 32)))))) (c (c (c (c (c (q . 33) (q . 34)) (c (q . 35) (q . 36))) (c (c (q . 37) (q . 38)) (c (q . 39) (q . 40)))) (c (c (c (q . 41) (q . 42)) (c (q . 43) (q . 44))) (c (c (q . 45) (q . 46)) (c (q . 47) (q . 48))))) (c (c (c (c (q . 49) (q . 50)) (c (q . 51) (q . 52))) (c (c (q . 53) (q . 54)) (c (q . 55) (q . 56)))) (c (c (c (q . 57) (q . 58)) (c (q . 59) (q . 60))) (c (c (q . 61) (q . 62)) (c (q . 63) (c (q . 64) (q . 65)))))))) (c (c (c (c (c (c (q . 66) (q . 67)) (c (q . 68) (q . 69))) (c (c (q . 70) (q . 71)) (c (q . 72) (q . 73)))) (c (c (c (q . 74) (q . 75)) (c (q . 76) (q . 77))) (c (c (q . 78) (q . 79)) (c (q . 80) (q . 81))))) (c (c (c (c (q . 82) (q . 83)) (c (q . 84) (q . 85))) (c (c (q . 86) (q . 87)) (c (q . 88) (q . 89)))) (c (c (c (q . 90) (q . 91)) (c (q . 92) (q . 93))) (c (c (q . 94) (q . 95)) (c (q . 96) (q . 97)))))) (c (c (c (c (c (q . 98) (q . 99)) (c (q . 100) (q . 101))) (c (c (q . 102) (q . 103)) (c (q . 104) (q . 105)))) (c (c (c (q . 106) (q . 107)) (c (q . 108) (q . 109))) (c (c (q . 110) (q . 111)) (c (q . 112) (q . 113))))) (c (c (c (c (q . 114) (q . 115)) (c (q . 116) (q . 117))) (c (c (q . 118) (q . 119)) (c (q . 120) (q . 121)))) (c (c (c (q . 122) (q . 123)) (c (q . 124) (q . 125))) (c (c (q . 126) (q . 127)) (c (q . 128) (c (q . 129) (q . 130))))))))) (c (c (c (c (c (c (c (q . 131) (q . 132)) (c (q . 133) (q . 134))) (c (c (q . 135) (q . 136)) (c (q . 137) (q . 138)))) (c (c (c (q . 139) (q . 140)) (c (q . 141) (q . 142))) (c (c (q . 143) (q . 144)) (c (q . 145) (q . 146))))) (c (c (c (c (q . 147) (q . 148)) (c (q . 149) (q . 150))) (c (c (q . 151) (q . 152)) (c (q . 153) (q . 154)))) (c (c (c (q . 155) (q . 156)) (c (q . 157) (q . 158))) (c (c (q . 159) (q . 160)) (c (q . 161) (q . 162)))))) (c (c (c (c (c (q . 163) (q . 164)) (c (q . 165) (q . 166))) (c (c (q . 167) (q . 168)) (c (q . 169) (q . 170)))) (c (c (c (q . 171) (q . 172)) (c (q . 173) (q . 174))) (c (c (q . 175) (q . 176)) (c (q . 177) (q . 178))))) (c (c (c (c (q . 179) (q . 180)) (c (q . 181) (q . 182))) (c (c (q . 183) (q . 184)) (c (q . 185) (q . 186)))) (c (c (c (q . 187) (q . 188)) (c (q . 189) (q . 190))) (c (c (q . 191) (q . 192)) (c (q . 193) (c (q . 194) (q . 195)))))))) (c (c (c (c (c (c (q . 196) (q . 197)) (c (q . 198) (q . 199))) (c (c (q . 200) (q . 201)) (c (q . 202) (q . 203)))) (c (c (c (q . 204) (q . 205)) (c (q . 206) (q . 207))) (c (c (q . 208) (q . 209)) (c (q . 210) (q . 211))))) (c (c (c (c (q . 212) (q . 213)) (c (q . 214) (q . 215))) (c (c (q . 216) (q . 217)) (c (q . 218) (q . 219)))) (c (c (c (q . 220) (q . 221)) (c (q . 222) (q . 223))) (c (c (q . 224) (q . 225)) (c (q . 226) (q . 227)))))) (c (c (c (c (c (q . 228) (q . 229)) (c (q . 230) (q . 231))) (c (c (q . 232) (q . 233)) (c (q . 234) (q . 235)))) (c (c (c (q . 236) (q . 237)) (c (q . 238) (q . 239))) (c (c (q . 240) (q . 241)) (c (q . 242) (q . 243))))) (c (c (c (c (q . 244) (q . 245)) (c (q . 246) (q . 247))) (c (c (q . 248) (q . 249)) (c (q . 250) (q . 251)))) (c (c (c (q . 252) (q . 253)) (c (q . 254) (q . 255))) (c (c (q . 256) (q . 257)) (c (q . 258) (c (q . 259) (q . 260)))))))))))) (c (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 256 384) 320) 448) 288) 416) 352) 480) 272) 400) 336) 464) 304) 432) 368) 496) 264) 392) 328) 456) 296) 424) 360) 488) 280) 408) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 344 472) 312) 440) 376) 504) 260) 388) 324) 452) 292) 420) 356) 484) 276) 404) 340) 468) 308) 436) 372) 500) 268) 396) 332) 460) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 300 428) 364) 492) 284) 412) 348) 476) 316) 444) 380) 764) 1020) 258) 386) 322) 450) 290) 418) 354) 482) 274) 402) 338) 466) 306) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 434 370) 498) 266) 394) 330) 458) 298) 426) 362) 490) 282) 410) 346) 474) 314) 442) 378) 506) 262) 390) 326) 454) 294) 422) 358) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 486 278) 406) 342) 470) 310) 438) 374) 502) 270) 398) 334) 462) 302) 430) 366) 494) 286) 414) 350) 478) 318) 446) 382) 766) 1022) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 257 385) 321) 449) 289) 417) 353) 481) 273) 401) 337) 465) 305) 433) 369) 497) 265) 393) 329) 457) 297) 425) 361) 489) 281) 409) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 345 473) 313) 441) 377) 505) 261) 389) 325) 453) 293) 421) 357) 485) 277) 405) 341) 469) 309) 437) 373) 501) 269) 397) 333) 461) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 301 429) 365) 493) 285) 413) 349) 477) 317) 445) 381) 765) 1021) 259) 387) 323) 451) 291) 419) 355) 483) 275) 403) 339) 467) 307) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 435 371) 499) 267) 395) 331) 459) 299) 427) 363) 491) 283) 411) 347) 475) 315) 443) 379) 507) 263) 391) 327) 455) 295) 423) 359) ()) (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 487 279) 407) 343) 471) 311) 439) 375) 503) 271) 399) 335) 463) 303) 431) 367) 495) 287) 415) 351) 479) 319) 447) 383) 767) 1023) ()) (q) (q 8 (q . "assertion failed at many_params_binary_tree.rue:780:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:752:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:724:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:696:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:668:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:640:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:612:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:584:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:556:5"))) 1) (q 8 (q . "assertion failed at many_params_binary_tree.rue:528:5"))) 1) ())) output: () runtime_cost: 7926 byte_cost: 3708000 diff --git a/tests/stress/many_params_non_overflow.rue b/tests/stress/many_params_non_overflow.rue index f031cbab..8b03f244 100644 --- a/tests/stress/many_params_non_overflow.rue +++ b/tests/stress/many_params_non_overflow.rue @@ -3,11 +3,65 @@ test fn closure() -> Any { } test fn call_binary_tree() { - function(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + function( + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); } test fn call_extern() { - function_extern(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); + function_extern( + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ); } fn function( @@ -38,7 +92,34 @@ fn function( y: Int, z: Int, ) { - assert (a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z) > 0; + assert ( + a + + b + + c + + d + + e + + f + + g + + h + + i + + j + + k + + l + + m + + n + + o + + p + + q + + r + + s + + t + + u + + v + + w + + x + + y + + z + ) > 0; } extern fn function_extern( @@ -69,5 +150,32 @@ extern fn function_extern( y: Int, z: Int, ) { - assert (a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z) > 0; + assert ( + a + + b + + c + + d + + e + + f + + g + + h + + i + + j + + k + + l + + m + + n + + o + + p + + q + + r + + s + + t + + u + + v + + w + + x + + y + + z + ) > 0; } diff --git a/tests/stress/many_params_non_overflow.yaml b/tests/stress/many_params_non_overflow.yaml index 4982363a..852d6cc5 100644 --- a/tests/stress/many_params_non_overflow.yaml +++ b/tests/stress/many_params_non_overflow.yaml @@ -1,22 +1,22 @@ tests: - name: closure program: (q 2 (i (> (+ 2 5 11 23 47 95 -65 383 767 1535 3071 6143 12287 24575 -16385 0x017fff 0x02ffff 0x05ffff 0x0bffff 0x17ffff 0x2fffff 0x5fffff 0xbfffff 0x017fffff 0x02ffffff 0x05ffffff) ()) (q) (q 8)) 1) - debug_program: (a (q 4 (q . 2) (c (c (q . 1) 2) (c (q . 1) ()))) (c (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 2 5) 11) 23) 47) 95) -65) 383) 767) 1535) 3071) 6143) 12287) 24575) -16385) 0x017fff) 0x02ffff) 0x05ffff) 0x0bffff) 0x17ffff) 0x2fffff) 0x5fffff) 0xbfffff) 0x017fffff) 0x02ffffff) 0x05ffffff) ()) (q) (q 8 (q . "assertion failed at many_params_non_overflow.rue:41:5"))) 1) ())) + debug_program: (a (q 4 (q . 2) (c (c (q . 1) 2) (c (q . 1) ()))) (c (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 2 5) 11) 23) 47) 95) -65) 383) 767) 1535) 3071) 6143) 12287) 24575) -16385) 0x017fff) 0x02ffff) 0x05ffff) 0x0bffff) 0x17ffff) 0x2fffff) 0x5fffff) 0xbfffff) 0x017fffff) 0x02ffffff) 0x05ffffff) ()) (q) (q 8 (q . "assertion failed at many_params_non_overflow.rue:95:5"))) 1) ())) output: (a (i (> (+ 2 5 11 23 47 95 -65 383 767 1535 3071 6143 12287 24575 -16385 0x017fff 0x02ffff 0x05ffff 0x0bffff 0x17ffff 0x2fffff 0x5fffff 0xbfffff 0x017fffff 0x02ffffff 0x05ffffff) ()) (q) (q 8)) 1) - debug_output: (a (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 2 5) 11) 23) 47) 95) -65) 383) 767) 1535) 3071) 6143) 12287) 24575) -16385) 0x017fff) 0x02ffff) 0x05ffff) 0x0bffff) 0x17ffff) 0x2fffff) 0x5fffff) 0xbfffff) 0x017fffff) 0x02ffffff) 0x05ffffff) ()) (q) (q 8 (q . "assertion failed at many_params_non_overflow.rue:41:5"))) 1) 1) + debug_output: (a (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 2 5) 11) 23) 47) 95) -65) 383) 767) 1535) 3071) 6143) 12287) 24575) -16385) 0x017fff) 0x02ffff) 0x05ffff) 0x0bffff) 0x17ffff) 0x2fffff) 0x5fffff) 0xbfffff) 0x017fffff) 0x02ffffff) 0x05ffffff) ()) (q) (q 8 (q . "assertion failed at many_params_non_overflow.rue:95:5"))) 1) 1) runtime_cost: 20 byte_cost: 1632000 total_cost: 1632020 - name: call_binary_tree program: (i () (a (i (> (q . 351) ()) (q) (q 8)) 1) ()) - debug_program: (a (q 3 (q . 1) () (a 2 (c (c (c (c (q . 1) (c (q . 2) (q . 3))) (c (q . 4) (c (q . 5) (q . 6)))) (c (c (q . 7) (c (q . 8) (q . 9))) (c (c (q . 10) (q . 11)) (c (q . 12) (q . 13))))) (c (c (c (q . 14) (c (q . 15) (q . 16))) (c (q . 17) (c (q . 18) (q . 19)))) (c (c (q . 20) (c (q . 21) (q . 22))) (c (c (q . 23) (q . 24)) (c (q . 25) (q . 26)))))))) (c (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 16 40) 56) 20) 44) 60) 18) 42) 58) 38) 54) 46) 62) 17) 41) 57) 21) 45) 61) 19) 43) 59) 39) 55) 47) 63) ()) (q) (q 8 (q . "assertion failed at many_params_non_overflow.rue:41:5"))) 1) ())) + debug_program: (a (q 3 (q . 1) () (a 2 (c (c (c (c (q . 1) (c (q . 2) (q . 3))) (c (q . 4) (c (q . 5) (q . 6)))) (c (c (q . 7) (c (q . 8) (q . 9))) (c (c (q . 10) (q . 11)) (c (q . 12) (q . 13))))) (c (c (c (q . 14) (c (q . 15) (q . 16))) (c (q . 17) (c (q . 18) (q . 19)))) (c (c (q . 20) (c (q . 21) (q . 22))) (c (c (q . 23) (q . 24)) (c (q . 25) (q . 26)))))))) (c (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 16 40) 56) 20) 44) 60) 18) 42) 58) 38) 54) 46) 62) 17) 41) 57) 21) 45) 61) 19) 43) 59) 39) 55) 47) 63) ()) (q) (q 8 (q . "assertion failed at many_params_non_overflow.rue:95:5"))) 1) ())) output: () runtime_cost: 942 byte_cost: 468000 total_cost: 468942 - name: call_extern program: (i () (a (i (> (q . 351) ()) (q) (q 8)) 1) ()) - debug_program: (a (q 3 (q . 1) () (a 2 (c (q . 1) (c (q . 2) (c (q . 3) (c (q . 4) (c (q . 5) (c (q . 6) (c (q . 7) (c (q . 8) (c (q . 9) (c (q . 10) (c (q . 11) (c (q . 12) (c (q . 13) (c (q . 14) (c (q . 15) (c (q . 16) (c (q . 17) (c (q . 18) (c (q . 19) (c (q . 20) (c (q . 21) (c (q . 22) (c (q . 23) (c (q . 24) (c (q . 25) (c (q . 26) ())))))))))))))))))))))))))))) (c (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 2 5) 11) 23) 47) 95) -65) 383) 767) 1535) 3071) 6143) 12287) 24575) -16385) 0x017fff) 0x02ffff) 0x05ffff) 0x0bffff) 0x17ffff) 0x2fffff) 0x5fffff) 0xbfffff) 0x017fffff) 0x02ffffff) 0x05ffffff) ()) (q) (q 8 (q . "assertion failed at many_params_non_overflow.rue:72:5"))) 1) ())) + debug_program: (a (q 3 (q . 1) () (a 2 (c (q . 1) (c (q . 2) (c (q . 3) (c (q . 4) (c (q . 5) (c (q . 6) (c (q . 7) (c (q . 8) (c (q . 9) (c (q . 10) (c (q . 11) (c (q . 12) (c (q . 13) (c (q . 14) (c (q . 15) (c (q . 16) (c (q . 17) (c (q . 18) (c (q . 19) (c (q . 20) (c (q . 21) (c (q . 22) (c (q . 23) (c (q . 24) (c (q . 25) (c (q . 26) ())))))))))))))))))))))))))))) (c (q 2 (i (> (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ (+ 2 5) 11) 23) 47) 95) -65) 383) 767) 1535) 3071) 6143) 12287) 24575) -16385) 0x017fff) 0x02ffff) 0x05ffff) 0x0bffff) 0x17ffff) 0x2fffff) 0x5fffff) 0xbfffff) 0x017fffff) 0x02ffffff) 0x05ffffff) ()) (q) (q 8 (q . "assertion failed at many_params_non_overflow.rue:153:5"))) 1) ())) output: () runtime_cost: 942 byte_cost: 468000 diff --git a/tests/symbols/closures.rue b/tests/symbols/closures.rue index 44723615..5fca6212 100644 --- a/tests/symbols/closures.rue +++ b/tests/symbols/closures.rue @@ -65,6 +65,7 @@ fn create_adder(a: Int) -> fn(b: Int) -> Int { } const CAPTURE_1: Int = 42; + const CAPTURE_2: Int = 34; fn tree_params(a: Int, b: Int, c: Int, d: Int) -> Int { diff --git a/tests/symbols/closures.yaml b/tests/symbols/closures.yaml index 573f91e2..8cd0299f 100644 --- a/tests/symbols/closures.yaml +++ b/tests/symbols/closures.yaml @@ -67,7 +67,7 @@ tests: total_cost: 711770 - name: closure_sequential_captures program: (a (q 2 (i (= (a (c (q . 2) (c (c (q . 1) (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 95 (q . 4)) (q 16 5 2) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 6) (c (q . 1) ()))) ()))) ()))) (q 1 2 3 4)) (q . 76)) (q 2 (i (= (a (c (q . 2) (c (c (q . 1) (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 63 (q . 4)) (q 16 5 2) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 6) (c (q . 1) ()))) ()))) ()))) (q 1 2 3 . 4)) (q . 76)) (q) (q 8)) 1) (q 8)) 1) (q (all . 42))) - debug_program: (a (q 2 (q 2 (q 2 (i (= 4 (q . 76)) (q 2 (i (= 6 (q . 76)) (q) (q 8 (q . "assertion failed at closures.rue:60:5"))) 1) (q 8 (q . "assertion failed at closures.rue:59:5"))) 1) (c (c (a 4 (c (q . 1) (c (q . 2) (c (q . 3) (c (q . 4) ()))))) (a 6 (c (q . 1) (c (q . 2) (c (q . 3) (q . 4)))))) 1)) (c (c (c (q . 2) (c (c (q . 1) 30) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 10) (c (q . 1) ()))) ()))) ()))) (c (q . 2) (c (c (q . 1) 22) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 10) (c (q . 1) ()))) ()))) ())))) 1)) (c (c (q . 34) (c (q . 42) (c (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 63 (q . 4)) (q 16 5 2) (q 8 (q . "assertion failed at closures.rue:82:5"))) 1) (q 8 (q . "assertion failed at closures.rue:81:5"))) 1) (q 8 (q . "assertion failed at closures.rue:80:5"))) 1) (q 8 (q . "assertion failed at closures.rue:79:5"))) 1) (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 95 (q . 4)) (q 16 5 2) (q 8 (q . "assertion failed at closures.rue:74:5"))) 1) (q 8 (q . "assertion failed at closures.rue:73:5"))) 1) (q 8 (q . "assertion failed at closures.rue:72:5"))) 1) (q 8 (q . "assertion failed at closures.rue:71:5"))) 1)))) ())) + debug_program: (a (q 2 (q 2 (q 2 (i (= 4 (q . 76)) (q 2 (i (= 6 (q . 76)) (q) (q 8 (q . "assertion failed at closures.rue:60:5"))) 1) (q 8 (q . "assertion failed at closures.rue:59:5"))) 1) (c (c (a 4 (c (q . 1) (c (q . 2) (c (q . 3) (c (q . 4) ()))))) (a 6 (c (q . 1) (c (q . 2) (c (q . 3) (q . 4)))))) 1)) (c (c (c (q . 2) (c (c (q . 1) 30) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 10) (c (q . 1) ()))) ()))) ()))) (c (q . 2) (c (c (q . 1) 22) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 10) (c (q . 1) ()))) ()))) ())))) 1)) (c (c (q . 34) (c (q . 42) (c (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 63 (q . 4)) (q 16 5 2) (q 8 (q . "assertion failed at closures.rue:83:5"))) 1) (q 8 (q . "assertion failed at closures.rue:82:5"))) 1) (q 8 (q . "assertion failed at closures.rue:81:5"))) 1) (q 8 (q . "assertion failed at closures.rue:80:5"))) 1) (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 95 (q . 4)) (q 16 5 2) (q 8 (q . "assertion failed at closures.rue:75:5"))) 1) (q 8 (q . "assertion failed at closures.rue:74:5"))) 1) (q 8 (q . "assertion failed at closures.rue:73:5"))) 1) (q 8 (q . "assertion failed at closures.rue:72:5"))) 1)))) ())) output: () runtime_cost: 8701 byte_cost: 6156000 diff --git a/tests/symbols/complex_constants.rue b/tests/symbols/complex_constants.rue index 3097cd6f..081abf89 100644 --- a/tests/symbols/complex_constants.rue +++ b/tests/symbols/complex_constants.rue @@ -1,6 +1,9 @@ const HELLO: String = "Hello"; + const WORLD: String = "world"; + const GREETING: String = greet(WORLD); + const MESSAGE: String = GREETING + " How are you doing?"; fn main() -> String { diff --git a/tests/symbols/inline_constants.rue b/tests/symbols/inline_constants.rue index c7e912d9..b0d19b27 100644 --- a/tests/symbols/inline_constants.rue +++ b/tests/symbols/inline_constants.rue @@ -1,4 +1,5 @@ const NUM: Int = 42; + inline const NUM_INLINE: Int = 42; test fn implicit_inline_binding() -> Int { diff --git a/tests/symbols/inline_functions.rue b/tests/symbols/inline_functions.rue index 243b0fd2..35d74a05 100644 --- a/tests/symbols/inline_functions.rue +++ b/tests/symbols/inline_functions.rue @@ -33,4 +33,3 @@ test fn double_param() -> Int { test fn explicit_inline_double_param() -> Int { double_param_ref_inline(42) } - diff --git a/tests/symbols/invalid_recursion.rue b/tests/symbols/invalid_recursion.rue index 7b5bac2b..4990569f 100644 --- a/tests/symbols/invalid_recursion.rue +++ b/tests/symbols/invalid_recursion.rue @@ -11,6 +11,9 @@ inline fn factorial(num: Int) -> Int { } const A: Int = A; + const B: Int = C; + const C: Int = B; + inline const D: Int = D; diff --git a/tests/symbols/invalid_recursion.yaml b/tests/symbols/invalid_recursion.yaml index b486c56a..3d51d71b 100644 --- a/tests/symbols/invalid_recursion.yaml +++ b/tests/symbols/invalid_recursion.yaml @@ -1,6 +1,6 @@ diagnostics: - Function `factorial` cannot be inline, since it references itself at invalid_recursion.rue:5:11 - Constant `A` references itself at invalid_recursion.rue:13:7 -- Constant `B` references itself at invalid_recursion.rue:14:7 -- Constant `C` references itself at invalid_recursion.rue:15:7 -- Constant `D` references itself at invalid_recursion.rue:16:14 +- Constant `B` references itself at invalid_recursion.rue:15:7 +- Constant `C` references itself at invalid_recursion.rue:17:7 +- Constant `D` references itself at invalid_recursion.rue:19:14 diff --git a/tests/symbols/super_errors.rue b/tests/symbols/super_errors.rue index 8d5dfeb3..168d8f46 100644 --- a/tests/symbols/super_errors.rue +++ b/tests/symbols/super_errors.rue @@ -3,5 +3,7 @@ mod a { } import super::x; + import super; + import super::super; diff --git a/tests/symbols/super_errors.yaml b/tests/symbols/super_errors.yaml index dd676561..ba55a7a3 100644 --- a/tests/symbols/super_errors.yaml +++ b/tests/symbols/super_errors.yaml @@ -1,10 +1,10 @@ diagnostics: - Unresolved `super`, there is no parent module at super_errors.rue:2:12 - Unresolved `super`, there is no parent module at super_errors.rue:5:8 -- Cannot end path in `super` at super_errors.rue:6:8 -- Unresolved `super`, there is no parent module at super_errors.rue:7:8 -- Cannot end path in `super` at super_errors.rue:7:15 +- Cannot end path in `super` at super_errors.rue:7:8 +- Unresolved `super`, there is no parent module at super_errors.rue:9:8 +- Cannot end path in `super` at super_errors.rue:9:15 - Unresolved import `x` at super_errors.rue:5:15 -- Unresolved import `super` at super_errors.rue:6:8 -- Unresolved import `super` at super_errors.rue:7:15 +- Unresolved import `super` at super_errors.rue:7:8 +- Unresolved import `super` at super_errors.rue:9:15 - Unused import `a` at super_errors.rue:2:19 diff --git a/tests/types/struct_error.rue b/tests/types/struct_error.rue index 54389d2a..24057944 100644 --- a/tests/types/struct_error.rue +++ b/tests/types/struct_error.rue @@ -4,8 +4,5 @@ struct Point { } test fn struct_error() -> Point { - Point { - x: "1", - y: 2, - } + Point { x: "1", y: 2, } } diff --git a/tests/types/struct_error.yaml b/tests/types/struct_error.yaml index 2638c7a8..a02e7784 100644 --- a/tests/types/struct_error.yaml +++ b/tests/types/struct_error.yaml @@ -1,2 +1,2 @@ diagnostics: -- Cannot assign `"1"` to `Int` without a cast, since they are semantically different at struct_error.rue:8:9 +- Cannot assign `"1"` to `Int` without a cast, since they are semantically different at struct_error.rue:7:13 diff --git a/tests/warnings/always_condition.rue b/tests/warnings/always_condition.rue index 18ff77c2..49323ff3 100644 --- a/tests/warnings/always_condition.rue +++ b/tests/warnings/always_condition.rue @@ -1,7 +1,7 @@ fn main() -> Int { assert true; assert !false; - + if false { 0 } else { diff --git a/tests/warnings/empty_generic_parameters.rue b/tests/warnings/empty_generic_parameters.rue index 42f7c031..6890c251 100644 --- a/tests/warnings/empty_generic_parameters.rue +++ b/tests/warnings/empty_generic_parameters.rue @@ -1,4 +1,4 @@ -fn main() -> Alias<>{ +fn main() -> Alias<> { function(); Struct {} } diff --git a/tests/warnings/unnecessary_guard.rue b/tests/warnings/unnecessary_guard.rue index ac3fa9cd..2d93c9db 100644 --- a/tests/warnings/unnecessary_guard.rue +++ b/tests/warnings/unnecessary_guard.rue @@ -35,4 +35,3 @@ struct B { } type Alias = A; - diff --git a/tests/warnings/unused_imports.rue b/tests/warnings/unused_imports.rue index 6168bc59..b5b95285 100644 --- a/tests/warnings/unused_imports.rue +++ b/tests/warnings/unused_imports.rue @@ -31,12 +31,19 @@ mod private { mod empty {} import single::a; + import glob::*; + import shadow_1::c; + import shadow_1::*; + import shadow_2::*; + import shadow_2::d; + import private::*; + import empty::*; mod deeply { @@ -50,7 +57,9 @@ mod deeply { } import deeply::nested; + import nested::module; + import module::used; fn main() -> Int { diff --git a/tests/warnings/unused_imports.yaml b/tests/warnings/unused_imports.yaml index ce9c1be7..c0c7812d 100644 --- a/tests/warnings/unused_imports.yaml +++ b/tests/warnings/unused_imports.yaml @@ -5,14 +5,14 @@ runtime_cost: 20 byte_cost: 36000 total_cost: 36020 diagnostics: -- Unused import `d` at unused_imports.rue:38:18 -- Unused import `*` at unused_imports.rue:36:18 -- Unused import `*` at unused_imports.rue:39:17 -- Unused import `*` at unused_imports.rue:40:15 +- Unused import `d` at unused_imports.rue:43:18 +- Unused import `*` at unused_imports.rue:39:18 +- Unused import `*` at unused_imports.rue:45:17 +- Unused import `*` at unused_imports.rue:47:15 - Unused import `a` at unused_imports.rue:33:16 -- Unused import `*` at unused_imports.rue:34:14 -- Unused import `c` at unused_imports.rue:35:18 -- Unused import `*` at unused_imports.rue:37:18 +- Unused import `*` at unused_imports.rue:35:14 +- Unused import `c` at unused_imports.rue:37:18 +- Unused import `*` at unused_imports.rue:41:18 - Unused function `a` at unused_imports.rue:2:15 - Unused function `b` at unused_imports.rue:8:15 - Unused function `c` at unused_imports.rue:14:15 diff --git a/tests/warnings/unused_symbols.rue b/tests/warnings/unused_symbols.rue index 0f68a256..7df71a90 100644 --- a/tests/warnings/unused_symbols.rue +++ b/tests/warnings/unused_symbols.rue @@ -24,8 +24,11 @@ test fn tests() -> Int { } const USED_BY_MAIN: Int = 42; + const USED_BY_TESTS: Int = 34; + const USED_BY_BOTH: Int = 69; + const UNUSED: Int = 0; fn used_by_main() {} diff --git a/tests/warnings/unused_symbols.yaml b/tests/warnings/unused_symbols.yaml index 03b5806e..4c8435d8 100644 --- a/tests/warnings/unused_symbols.yaml +++ b/tests/warnings/unused_symbols.yaml @@ -14,7 +14,7 @@ tests: total_cost: 132142 diagnostics: - Unused constant `UNUSED_IN_MODULE` at unused_symbols.rue:3:18 -- Unused constant `UNUSED` at unused_symbols.rue:29:7 -- Unused function `unused` at unused_symbols.rue:37:4 +- Unused constant `UNUSED` at unused_symbols.rue:32:7 +- Unused function `unused` at unused_symbols.rue:40:4 - Unused binding `unused` at unused_symbols.rue:12:9 - Unused binding `unused` at unused_symbols.rue:20:9 diff --git a/tests/warnings/unused_types.rue b/tests/warnings/unused_types.rue index 82733bb3..c4cbc30a 100644 --- a/tests/warnings/unused_types.rue +++ b/tests/warnings/unused_types.rue @@ -1,12 +1,15 @@ type Recursive = Recursive; type A = B; + type B = A; type Used = 42; + type Unused = "Hello, world!"; const USED_VALUE: 42 = 42; + const UNUSED_VALUE: 34 = 34; struct UsedStruct { @@ -18,12 +21,15 @@ struct UnusedStruct { } type UsedAlias = UsedStruct; + type UnusedAlias = UnusedStruct; type Thing1 = 42; + type Thing2 = 42; const INNER: Thing1 = 42; + const OUTER: Thing2 = 42; struct Inner { diff --git a/tests/warnings/unused_types.yaml b/tests/warnings/unused_types.yaml index 6a3c0450..53e39dd4 100644 --- a/tests/warnings/unused_types.yaml +++ b/tests/warnings/unused_types.yaml @@ -27,8 +27,8 @@ tests: byte_cost: 12000 total_cost: 12044 diagnostics: -- Unused constant `UNUSED_VALUE` at unused_types.rue:10:7 +- Unused constant `UNUSED_VALUE` at unused_types.rue:13:7 - Unused type alias `Recursive` at unused_types.rue:1:6 -- Unused type alias `Unused` at unused_types.rue:7:6 -- Unused struct `UnusedStruct` at unused_types.rue:16:8 -- Unused type alias `UnusedAlias` at unused_types.rue:21:6 +- Unused type alias `Unused` at unused_types.rue:9:6 +- Unused struct `UnusedStruct` at unused_types.rue:19:8 +- Unused type alias `UnusedAlias` at unused_types.rue:25:6 From 847fc7195ae9ced004dbfe4a6a6ffbabf04ab440 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 16:09:17 -0400 Subject: [PATCH 02/21] Some fixes --- .../rue-compiler/src/std/curry_tree_hash.rue | 4 +- crates/rue-compiler/src/std/merkle.rue | 12 +- crates/rue-formatter/src/format.rs | 298 +++++++++++++++++- crates/rue-formatter/src/lib.rs | 95 +++++- crates/rue-formatter/src/tests.rs | 88 +++++- examples/brainfck.rue | 32 +- examples/fizz_buzz.rue | 2 +- examples/puzzles/cat.rue | 12 +- examples/puzzles/p2_delegated_conditions.rue | 2 +- examples/puzzles/partial_offer.rue | 12 +- examples/puzzles/singleton.rue | 21 +- tests/builtins.rue | 16 +- tests/builtins.yaml | 14 +- tests/imports/duplicate_imports.rue | 4 +- tests/imports/duplicate_imports.yaml | 4 +- tests/imports/glob_export.rue | 4 +- tests/imports/import_order.rue | 8 +- tests/imports/multi_namespace.rue | 4 +- tests/imports/symbol_import.rue | 4 +- tests/imports/type_import.rue | 4 +- tests/imports/unresolved_imports.rue | 4 +- tests/imports/unresolved_imports.yaml | 4 +- tests/std.rue | 10 +- tests/stress/many_params_binary_tree.rue | 2 +- tests/stress/many_params_non_overflow.rue | 4 +- tests/symbols/super_errors.rue | 8 +- tests/symbols/super_errors.yaml | 16 +- tests/types/struct_error.rue | 2 +- tests/warnings/unused_imports.rue | 44 +-- tests/warnings/unused_imports.yaml | 26 +- 30 files changed, 602 insertions(+), 158 deletions(-) diff --git a/crates/rue-compiler/src/std/curry_tree_hash.rue b/crates/rue-compiler/src/std/curry_tree_hash.rue index e7fc472e..799e4daa 100644 --- a/crates/rue-compiler/src/std/curry_tree_hash.rue +++ b/crates/rue-compiler/src/std/curry_tree_hash.rue @@ -12,7 +12,7 @@ fn apply_hash(mod_hash: Bytes32, environment_hash: Bytes32) -> Bytes32 { sha256( 2 as Bytes + tree_hash_atom(2 as Bytes) - + two_item_list_hash(quote_hash(mod_hash), environment_hash) + + two_item_list_hash(quote_hash(mod_hash), environment_hash), ) } @@ -20,7 +20,7 @@ fn update_hash_with_parameter(parameter_hash: Bytes32, environment_hash: Bytes32 sha256( 2 as Bytes + tree_hash_atom(4 as Bytes) - + two_item_list_hash(quote_hash(parameter_hash), environment_hash) + + two_item_list_hash(quote_hash(parameter_hash), environment_hash), ) } diff --git a/crates/rue-compiler/src/std/merkle.rue b/crates/rue-compiler/src/std/merkle.rue index 1b8f69b0..07f99321 100644 --- a/crates/rue-compiler/src/std/merkle.rue +++ b/crates/rue-compiler/src/std/merkle.rue @@ -27,13 +27,13 @@ export fn calculate_merkle_root( current_hash } else { additional_steps.first - } + }, ), - additional_steps.rest + additional_steps.rest, ) } -fn simplify_merkle_proof_after_leaf(leaf_hash: Bytes32, proof: MerkleProof,) -> Bytes32 { +fn simplify_merkle_proof_after_leaf(leaf_hash: Bytes32, proof: MerkleProof) -> Bytes32 { if proof.hashes is nil { return leaf_hash; } @@ -49,12 +49,12 @@ fn simplify_merkle_proof_after_leaf(leaf_hash: Bytes32, proof: MerkleProof,) -> leaf_hash } else { proof.hashes.first - } + }, ), - MerkleProof { path: proof.path >>> 1, hashes: proof.hashes.rest } + MerkleProof { path: proof.path >>> 1, hashes: proof.hashes.rest }, ) } -export inline fn simplify_merkle_proof(leaf: Bytes32, proof: MerkleProof,) -> Bytes32 { +export inline fn simplify_merkle_proof(leaf: Bytes32, proof: MerkleProof) -> Bytes32 { simplify_merkle_proof_after_leaf(tree_hash_atom(leaf), proof) } diff --git a/crates/rue-formatter/src/format.rs b/crates/rue-formatter/src/format.rs index d0add877..5ca18d61 100644 --- a/crates/rue-formatter/src/format.rs +++ b/crates/rue-formatter/src/format.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap, HashSet}; -use rue_ast::{AstDocument, AstNode}; +use rue_ast::{AstDocument, AstItem, AstNode}; use rue_parser::{SyntaxKind, SyntaxNode, T}; use crate::{ @@ -30,12 +30,32 @@ struct Layout { pairs: HashMap, block_openers: HashSet, braced_openers: HashSet, + trailing_comma_openers: HashSet, generic_tokens: HashSet, prefix_operators: HashSet, attached_openers: HashSet, item_ends: HashSet, groups: HashMap, binary_operators: HashMap>, + document_items: Vec, + import_groups: HashMap>, +} + +#[derive(Debug, Clone)] +struct DocumentItem { + start: usize, + end: usize, + import_group: Option, + compact_group: Option, + sort_key: String, + original_index: usize, +} + +#[derive(Debug, Clone)] +struct ImportGroupItem { + start: usize, + end: usize, + sort_key: String, } pub(crate) fn format_document( @@ -52,7 +72,36 @@ pub(crate) fn format_document( }; let mut docs = vec![formatter.gap_doc(&stream.gaps[0], Separator::None)]; - docs.push(formatter.span(0, stream.tokens.len())?); + let items = formatter.layout.document_items.clone(); + for (position, item) in items.iter().enumerate() { + if position > 0 || item.start != 0 { + let separator = if position == 0 { + Separator::None + } else { + let previous = &items[position - 1]; + match (previous.import_group, item.import_group) { + (Some(left), Some(right)) if left == right => Separator::Hard, + (None, None) + if previous.compact_group.is_some() + && previous.compact_group == item.compact_group + && (item.start == 0 + || stream.gaps[item.start].newlines <= 1) => + { + Separator::Hard + } + _ => Separator::Empty, + } + }; + if item.start == 0 { + docs.push(separator_doc(separator)); + } else { + let mut gap = stream.gaps[item.start].clone(); + gap.newlines = 0; + docs.push(formatter.gap_doc(&gap, separator)); + } + } + docs.push(formatter.span(item.start, item.end)?); + } if !stream.tokens.is_empty() { docs.push(formatter.gap_doc( stream.gaps.last().expect("a trailing gap always exists"), @@ -202,6 +251,10 @@ impl Formatter<'_> { let open_doc = self.token(open); let close_doc = self.token(close); let inner_start = open + 1; + let supports_trailing_comma = self + .layout + .trailing_comma_openers + .contains(&self.stream.tokens[open].start); if inner_start == close { let gap = self.gap_doc_with_comments( @@ -218,7 +271,24 @@ impl Formatter<'_> { return Ok(Doc::concat([open_doc, gap, close_doc]).group()); } - let inner = self.span(inner_start, close)?; + if let Some(items) = self.layout.import_groups.get(&open).cloned() { + return self.import_group(open_doc, close_doc, close, &items); + } + + let source_trailing_comma = + supports_trailing_comma && self.stream.tokens[close - 1].kind == T![,]; + let inner_end = close - usize::from(source_trailing_comma); + let inner = self.span(inner_start, inner_end)?; + let comma = if source_trailing_comma { + let leading = self.gap_doc(&self.stream.gaps[inner_end], Separator::None); + self.consumed_tokens += 1; + Doc::concat([leading, Doc::if_break(Doc::text(","), Doc::Nil)]) + } else if supports_trailing_comma { + Doc::if_break(Doc::text(","), Doc::Nil) + } else { + Doc::Nil + }; + let inner = Doc::concat([inner, comma]); let leading_gap = &self.stream.gaps[inner_start]; let trailing_gap = &self.stream.gaps[close]; let leading_comment_starts_line = leading_gap @@ -286,6 +356,54 @@ impl Formatter<'_> { }) } + fn import_group( + &mut self, + open: Doc, + close: Doc, + close_index: usize, + items: &[ImportGroupItem], + ) -> Result { + let mut docs = Vec::new(); + for (position, item) in items.iter().enumerate() { + let requested = if position == 0 { + Separator::None + } else { + Separator::Soft + }; + let mut gap = self.stream.gaps[item.start].clone(); + gap.newlines = 0; + docs.push(self.gap_doc(&gap, requested)); + docs.push(self.span(item.start, item.end)?); + + if self + .stream + .tokens + .get(item.end) + .is_some_and(|token| token.kind == T![,]) + { + let comma_gap = self.gap_doc(&self.stream.gaps[item.end], Separator::None); + docs.push(comma_gap); + self.consumed_tokens += 1; + } + + if position + 1 < items.len() { + docs.push(Doc::text(",")); + } else { + docs.push(Doc::if_break(Doc::text(","), Doc::Nil)); + } + } + + let trailing = self.gap_doc(&self.stream.gaps[close_index], Separator::None); + Ok(Doc::concat([ + open, + Doc::concat([Doc::if_break(Doc::hard_line(), Doc::Nil), Doc::concat(docs)]).indent(), + trailing, + Doc::if_break(Doc::hard_line(), Doc::Nil), + close, + ]) + .group()) + } + fn token(&mut self, index: usize) -> Doc { self.consumed_tokens += 1; Doc::text(self.stream.tokens[index].text.clone()) @@ -415,7 +533,9 @@ impl Layout { let root = document.syntax(); let mut block_openers = HashSet::new(); let mut braced_openers = HashSet::new(); + let mut trailing_comma_openers = HashSet::new(); let mut generic_tokens = HashSet::new(); + let mut generic_pairs = Vec::new(); let mut prefix_operators = HashSet::new(); let mut attached_openers = HashSet::new(); @@ -425,14 +545,35 @@ impl Layout { if let Some(token) = significant_tokens(&node).find(|token| token.kind() == T!['{']) { - block_openers.insert(usize::from(token.text_range().start())); + let start = usize::from(token.text_range().start()); + block_openers.insert(start); + if node.kind() == SyntaxKind::StructItem { + trailing_comma_openers.insert(start); + } } } SyntaxKind::StructInitializerExpr | SyntaxKind::StructBinding => { if let Some(token) = significant_tokens(&node).find(|token| token.kind() == T!['{']) { - braced_openers.insert(usize::from(token.text_range().start())); + let start = usize::from(token.text_range().start()); + braced_openers.insert(start); + trailing_comma_openers.insert(start); + } + } + SyntaxKind::PairExpr + | SyntaxKind::PairType + | SyntaxKind::PairBinding + | SyntaxKind::ListExpr + | SyntaxKind::ListType + | SyntaxKind::ListBinding + | SyntaxKind::ImportPathSegment => { + if let Some(token) = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .find(|token| matches!(token.kind(), T!['('] | T!['['] | T!['{'])) + { + trailing_comma_openers.insert(usize::from(token.text_range().start())); } } SyntaxKind::GenericParameters | SyntaxKind::GenericArguments => { @@ -441,6 +582,20 @@ impl Layout { .filter(|token| is_generic_punctuation(token.kind())) .map(|token| usize::from(token.text_range().start())), ); + let direct_tokens: Vec<_> = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .filter(|token| !token.kind().is_trivia()) + .collect(); + if let (Some(open), Some(close)) = (direct_tokens.first(), direct_tokens.last()) + && open.kind() == T![<] + && close.kind() == T![>] + { + let open = usize::from(open.text_range().start()); + let close = usize::from(close.text_range().start()); + trailing_comma_openers.insert(open); + generic_pairs.push((open, close)); + } } SyntaxKind::PrefixExpr => { if let Some(token) = significant_tokens(&node) @@ -458,13 +613,25 @@ impl Layout { .filter_map(rowan::NodeOrToken::into_token) .find(|token| token.kind() == T!['(']) { - attached_openers.insert(usize::from(token.text_range().start())); + let start = usize::from(token.text_range().start()); + attached_openers.insert(start); + trailing_comma_openers.insert(start); } } _ => {} } } + for (open, close) in generic_pairs { + let Some(open) = stream.tokens.iter().position(|token| token.start == open) else { + continue; + }; + let Some(close) = stream.tokens.iter().position(|token| token.start == close) else { + continue; + }; + pairs.insert(open, close); + } + let mut item_ends = HashSet::new(); for item in document.items() { if let Some(token) = significant_tokens(item.syntax()).last() { @@ -486,6 +653,55 @@ impl Layout { .enumerate() .map(|(index, token)| (token.start, index)) .collect(); + let mut import_groups = HashMap::new(); + for node in root + .descendants() + .filter(|node| node.kind() == SyntaxKind::ImportPathSegment) + { + let Some(open) = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .find(|token| token.kind() == T!['{']) + .and_then(|token| { + token_indices + .get(&usize::from(token.text_range().start())) + .copied() + }) + else { + continue; + }; + let mut items = Vec::new(); + for path in node + .children() + .filter(|child| child.kind() == SyntaxKind::ImportPath) + { + let significant: Vec<_> = significant_tokens(&path).collect(); + let (Some(first), Some(last)) = (significant.first(), significant.last()) else { + continue; + }; + let Some(&start) = token_indices.get(&usize::from(first.text_range().start())) + else { + continue; + }; + let Some(&last) = token_indices.get(&usize::from(last.text_range().start())) else { + continue; + }; + let end = last + 1; + let sort_key = stream.tokens[start..end] + .iter() + .map(|token| token.text.as_str()) + .collect(); + items.push(ImportGroupItem { + start, + end, + sort_key, + }); + } + items.sort_by(|left, right| left.sort_key.cmp(&right.sort_key)); + if !items.is_empty() { + import_groups.insert(open, items); + } + } let mut groups = HashMap::new(); let mut binary_operators = HashMap::new(); for node in root @@ -524,16 +740,86 @@ impl Layout { } } + let mut document_items = Vec::new(); + let mut next_import_group = 0; + let mut previous_was_import = false; + for (original_index, item) in document.items().enumerate() { + let significant: Vec<_> = significant_tokens(item.syntax()).collect(); + let (Some(first), Some(last)) = (significant.first(), significant.last()) else { + continue; + }; + let Some(&start) = token_indices.get(&usize::from(first.text_range().start())) else { + continue; + }; + let Some(&last) = token_indices.get(&usize::from(last.text_range().start())) else { + continue; + }; + let mut end = last + 1; + if stream + .tokens + .get(end) + .is_some_and(|token| token.kind == T![;]) + { + end += 1; + } + + let is_import = matches!(&item, AstItem::ImportItem(_)); + let import_group = is_import.then(|| { + if !previous_was_import || (start > 0 && stream.gaps[start].newlines > 1) { + next_import_group += 1; + } + next_import_group + }); + previous_was_import = is_import; + let compact_group = matches!( + item.syntax().kind(), + SyntaxKind::ConstantItem | SyntaxKind::TypeAliasItem + ) + .then_some(item.syntax().kind()) + .filter(|_| !item.syntax().text().to_string().contains('\n')); + + let path_key = stream.tokens[start..end] + .iter() + .filter(|token| !matches!(token.kind, T![import] | T![export] | T![;])) + .map(|token| token.text.as_str()) + .collect::(); + let keyword_key = stream.tokens[start..end] + .first() + .map_or("", |token| token.text.as_str()); + document_items.push(DocumentItem { + start, + end, + import_group, + compact_group, + sort_key: format!("{path_key}\0{keyword_key}"), + original_index, + }); + } + document_items.sort_by( + |left, right| match (left.import_group, right.import_group) { + (Some(left_group), Some(right_group)) => left_group + .cmp(&right_group) + .then_with(|| left.sort_key.cmp(&right.sort_key)) + .then_with(|| left.original_index.cmp(&right.original_index)), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => left.original_index.cmp(&right.original_index), + }, + ); + Ok(Self { pairs, block_openers, braced_openers, + trailing_comma_openers, generic_tokens, prefix_operators, attached_openers, item_ends, groups, binary_operators, + document_items, + import_groups, }) } } diff --git a/crates/rue-formatter/src/lib.rs b/crates/rue-formatter/src/lib.rs index bcca5a1f..d3ef747c 100644 --- a/crates/rue-formatter/src/lib.rs +++ b/crates/rue-formatter/src/lib.rs @@ -145,16 +145,105 @@ fn verify_equivalence(before: &Parsed, after: &Parsed) -> Result<(), FormatError fn structural_signature(root: &SyntaxNode) -> Vec { let mut signature = Vec::new(); - for element in root.descendants_with_tokens() { + append_node_signature(root, &mut signature); + signature +} + +fn append_node_signature(node: &SyntaxNode, signature: &mut Vec) { + signature.push(format!("n:{:?}", node.kind())); + + if node.kind() == SyntaxKind::Document { + let mut imports = Vec::new(); + let mut items = Vec::new(); + for child in node.children() { + let mut child_signature = Vec::new(); + append_node_signature(&child, &mut child_signature); + if child.kind() == SyntaxKind::ImportItem { + imports.push(child_signature); + } else { + items.push(child_signature); + } + } + imports.sort(); + signature.extend(imports.into_iter().flatten()); + signature.extend(items.into_iter().flatten()); + return; + } + + let mut sorted_import_paths = if node.kind() == SyntaxKind::ImportPathSegment { + let mut paths: Vec<_> = node + .children() + .filter(|child| child.kind() == SyntaxKind::ImportPath) + .map(|child| { + let mut child_signature = Vec::new(); + append_node_signature(&child, &mut child_signature); + child_signature + }) + .collect(); + paths.sort(); + paths.into_iter() + } else { + Vec::new().into_iter() + }; + + for element in node.children_with_tokens() { match element { - rowan::NodeOrToken::Node(node) => signature.push(format!("n:{:?}", node.kind())), + rowan::NodeOrToken::Node(child) + if node.kind() == SyntaxKind::ImportPathSegment + && child.kind() == SyntaxKind::ImportPath => + { + signature.extend( + sorted_import_paths + .next() + .expect("each import path has a sorted signature"), + ); + } + rowan::NodeOrToken::Node(child) => append_node_signature(&child, signature), + rowan::NodeOrToken::Token(token) + if token.kind() == rue_parser::T![,] && is_optional_trailing_comma(&token) => {} rowan::NodeOrToken::Token(token) if !token.kind().is_trivia() => { signature.push(format!("t:{:?}:{}", token.kind(), token.text())); } rowan::NodeOrToken::Token(_) => {} } } - signature +} + +fn is_optional_trailing_comma(token: &rue_parser::SyntaxToken) -> bool { + let mut next = token.next_token(); + while next.as_ref().is_some_and(|token| token.kind().is_trivia()) { + next = next.and_then(|token| token.next_token()); + } + if !next.is_some_and(|token| { + matches!( + token.kind(), + rue_parser::T![')'] | rue_parser::T![']'] | rue_parser::T!['}'] | rue_parser::T![>] + ) + }) { + return false; + } + + token.parent_ancestors().any(|node| { + matches!( + node.kind(), + SyntaxKind::FunctionItem + | SyntaxKind::FunctionCallExpr + | SyntaxKind::LambdaExpr + | SyntaxKind::LambdaType + | SyntaxKind::PairExpr + | SyntaxKind::PairType + | SyntaxKind::PairBinding + | SyntaxKind::ListExpr + | SyntaxKind::ListType + | SyntaxKind::ListBinding + | SyntaxKind::StructItem + | SyntaxKind::StructInitializerExpr + | SyntaxKind::StructBinding + | SyntaxKind::ImportPathSegment + | SyntaxKind::GenericParameters + | SyntaxKind::GenericArguments + ) + }) } fn comment_signature(stream: &TokenStream) -> Vec<(SyntaxKind, &str)> { diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index a4843f0e..cde81815 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -33,10 +33,9 @@ fn types_bindings_generics_and_imports() { "import foo::*; import foo::{bar,baz,}; type Pair=(T,T,); fn take(...[a,{x:y}]:[T,...T])->fn(a:T)->T{}", expect![[r#" import foo::*; + import foo::{bar, baz}; - import foo::{bar, baz,}; - - type Pair = (T, T,); + type Pair = (T, T); fn take(...[a, { x: y }]: [T, ...T]) -> fn(a: T) -> T {} "#]], @@ -112,7 +111,7 @@ fn width_breaks_delimited_groups() { expect![[r#" fn main( first: VeryLongType, - second: VeryLongType + second: VeryLongType, ) -> VeryLongType { first(second, second) } @@ -157,7 +156,7 @@ fn standalone_comment_inside_broken_group() { fn main() { [ // explain - 1 + 1, ] } "#]], @@ -191,7 +190,7 @@ fn nested_delimiters_use_single_indent() { expect![[r#" fn main() { assert tree_hash( - fizz_buzz(1, 15) + fizz_buzz(1, 15), ) == tree_hash([1, 2, 3, 4, 5, 6]); } "#]] @@ -254,10 +253,85 @@ fn comparison_wraps_rhs_inside_logical_chain() { && second_value == call( first, second, - third + third, ); } "#]] .assert_eq(&output); assert_eq!(format_source(&output, &options).unwrap(), output); } + +#[test] +fn generic_trailing_commas_follow_layout() { + check( + "fn flat(){}", + expect![[r#" + fn flat() {} + "#]], + ); + + let options = FormatOptions { + max_width: 30, + ..FormatOptions::default() + }; + let output = format_source( + "fn example(){}", + &options, + ) + .unwrap(); + expect![[r#" + fn example< + FirstLongParameter, + SecondLongParameter, + >() {} + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn imports_are_hoisted_grouped_and_sorted() { + check( + "const VALUE:Int=1; import zebra; import beta::{zeta,alpha};\n\nimport delta; import charlie; fn main(){}", + expect![[r#" + import beta::{alpha, zeta}; + import zebra; + + import charlie; + import delta; + + const VALUE: Int = 1; + + fn main() {} + "#]], + ); + check( + "const VALUE:Int=1;\n// zebra\nimport zebra;\nimport alpha;", + expect![[r#" + import alpha; + // zebra + import zebra; + + const VALUE: Int = 1; + "#]], + ); +} + +#[test] +fn compact_items_are_grouped() { + check( + "const FIRST:Int=1;\nconst SECOND:Int=2;\n\nconst THIRD:Int=3;\nconst FOURTH:Int=4;\n\ntype A=Int;\ntype B=Int;\n\nfn main(){}", + expect![[r#" + const FIRST: Int = 1; + const SECOND: Int = 2; + + const THIRD: Int = 3; + const FOURTH: Int = 4; + + type A = Int; + type B = Int; + + fn main() {} + "#]], + ); +} diff --git a/examples/brainfck.rue b/examples/brainfck.rue index 3db8dc97..ccd24c7f 100644 --- a/examples/brainfck.rue +++ b/examples/brainfck.rue @@ -30,23 +30,23 @@ fn step(program: Program) -> Program { let source = substr(source, 1); if char == "+" { - Program { state: increment(state), source, } + Program { state: increment(state), source } } else if char == "-" { - Program { state: decrement(state), source, } + Program { state: decrement(state), source } } else if char == "<" { - Program { state: move_left(state), source, } + Program { state: move_left(state), source } } else if char == ">" { - Program { state: move_right(state), source, } + Program { state: move_right(state), source } } else if char == "," { - Program { state: input(state), source, } + Program { state: input(state), source } } else if char == "." { - Program { state: output(state), source, } + Program { state: output(state), source } } else if char == "[" { let length = block_length(source, 1); let block = substr(source, 0, length); let source = substr(source, length); - Program { state: loop(state, block), source, } + Program { state: loop(state, block), source } } else { Program { state, source } } @@ -91,24 +91,24 @@ inline fn update(state: State, tape: Tape) -> State { inline fn increment(state: State) -> State { inline let { tape } = state; - update(state, Tape { value: tape.value + 1, left: tape.left, right: tape.right, }) + update(state, Tape { value: tape.value + 1, left: tape.left, right: tape.right }) } inline fn decrement(state: State) -> State { inline let { tape } = state; - update(state, Tape { value: tape.value - 1, left: tape.left, right: tape.right, }) + update(state, Tape { value: tape.value - 1, left: tape.left, right: tape.right }) } inline fn move_left(state: State) -> State { inline let { tape } = state; if tape.left is nil { - update(state, Tape { value: 0, left: nil, right: (tape.value, tape.right), }) + update(state, Tape { value: 0, left: nil, right: (tape.value, tape.right) }) } else { update( state, - Tape { value: tape.left.first, left: tape.left.rest, right: (tape.value, tape.right), } + Tape { value: tape.left.first, left: tape.left.rest, right: (tape.value, tape.right) }, ) } } @@ -117,11 +117,11 @@ inline fn move_right(state: State) -> State { inline let { tape } = state; if tape.right is nil { - update(state, Tape { value: 0, left: (tape.value, tape.left), right: nil, }) + update(state, Tape { value: 0, left: (tape.value, tape.left), right: nil }) } else { update( state, - Tape { value: tape.right.first, left: (tape.value, tape.left), right: tape.right.rest, } + Tape { value: tape.right.first, left: (tape.value, tape.left), right: tape.right.rest }, ) } } @@ -129,14 +129,14 @@ inline fn move_right(state: State) -> State { inline fn output(state: State) -> State { inline let { tape, input, output } = state; - State { tape, input, output: output + tape.value as String, } + State { tape, input, output: output + tape.value as String } } inline fn input(state: State) -> State { inline let { tape, input, output } = state; State { - tape: Tape { value: substr(input, 0, 1) as Int, left: tape.left, right: tape.right, }, + tape: Tape { value: substr(input, 0, 1) as Int, left: tape.left, right: tape.right }, input: substr(input, 1), output, } @@ -144,7 +144,7 @@ inline fn input(state: State) -> State { inline fn run(source: String, input: String) -> String { let program = Program { - state: State { tape: Tape { value: 0, left: nil, right: nil, }, input, output: "", }, + state: State { tape: Tape { value: 0, left: nil, right: nil }, input, output: "" }, source, }; diff --git a/examples/fizz_buzz.rue b/examples/fizz_buzz.rue index a38b5579..bb60ecc2 100644 --- a/examples/fizz_buzz.rue +++ b/examples/fizz_buzz.rue @@ -57,6 +57,6 @@ test fn tests() { "13", "14", "FizzBuzz", - ] + ], ); } diff --git a/examples/puzzles/cat.rue b/examples/puzzles/cat.rue index b94c9c02..ca352586 100644 --- a/examples/puzzles/cat.rue +++ b/examples/puzzles/cat.rue @@ -82,7 +82,7 @@ inline const RING_MORPH_BYTE: Bytes = 0xcb; inline fn cat_puzzle_hash(cat_info: CatInfo, inner_puzzle_hash: Bytes32) -> Bytes32 { curry_tree_hash( cat_info.mod_hash, - [cat_info.mod_hash_hash, tree_hash_atom(cat_info.asset_id), inner_puzzle_hash] + [cat_info.mod_hash_hash, tree_hash_atom(cat_info.asset_id), inner_puzzle_hash], ) } @@ -99,7 +99,7 @@ fn main( extra_delta: Int, ) -> List { // For simplicity, we'll pack these values into a struct. - let cat_info = CatInfo { mod_hash, mod_hash_hash: tree_hash_atom(mod_hash), asset_id, }; + let cat_info = CatInfo { mod_hash, mod_hash_hash: tree_hash_atom(mod_hash), asset_id }; // Calculate the inner puzzle hash and conditions. let morph = morph_conditions(inner_puzzle(...inner_solution), cat_info, nil); @@ -156,7 +156,7 @@ fn main( lineage_proof, extra_delta, conditions, - morph.tail_info.tail_solution + morph.tail_info.tail_solution, ); // Prepend the TAIL's conditions. @@ -198,7 +198,7 @@ fn morph_conditions( ) -> Morph { // If there are no conditions, return an empty morph. if conditions is nil { - return Morph { conditions: nil, sum: 0, tail_info: tail_info, }; + return Morph { conditions: nil, sum: 0, tail_info: tail_info }; } inline let condition = conditions.first; @@ -207,10 +207,10 @@ fn morph_conditions( let rest = morph_conditions( conditions.rest, cat_info, - TailInfo { tail_puzzle: condition.tail_puzzle, tail_solution: condition.tail_solution, } + TailInfo { tail_puzzle: condition.tail_puzzle, tail_solution: condition.tail_solution }, ); - return Morph { conditions: rest.conditions, sum: rest.sum, tail_info: rest.tail_info, }; + return Morph { conditions: rest.conditions, sum: rest.sum, tail_info: rest.tail_info }; } let morphed = if condition is CreateCoin { diff --git a/examples/puzzles/p2_delegated_conditions.rue b/examples/puzzles/p2_delegated_conditions.rue index d47f2357..e98dba96 100644 --- a/examples/puzzles/p2_delegated_conditions.rue +++ b/examples/puzzles/p2_delegated_conditions.rue @@ -1,4 +1,4 @@ fn main(public_key: PublicKey, conditions: List) -> List { - let agg_sig = AggSigMe { public_key, message: tree_hash(conditions), }; + let agg_sig = AggSigMe { public_key, message: tree_hash(conditions) }; [agg_sig, ...conditions] } diff --git a/examples/puzzles/partial_offer.rue b/examples/puzzles/partial_offer.rue index c9799aa5..3798251d 100644 --- a/examples/puzzles/partial_offer.rue +++ b/examples/puzzles/partial_offer.rue @@ -45,9 +45,9 @@ fn main( raise "Other asset amount is too small"; }, [receiver_puzzle_hash], - ] - ] - ) + ], + ], + ), ), }; @@ -68,10 +68,10 @@ fn main( CreateCoin { puzzle_hash: proof.inner_puzzle_hash, amount: new_amount, - memos: Memos { value: [proof.inner_puzzle_hash], }, + memos: Memos { value: [proof.inner_puzzle_hash] }, } } else { - Remark { rest: nil, } + Remark { rest: nil } }; let inner_conditions = inner_puzzle(...inner_puzzle_solution); @@ -82,5 +82,5 @@ fn main( inner_conditions }; - [assert_puzzle_announcement, assert_my_coin_id, recreate, ...rest,] + [assert_puzzle_announcement, assert_my_coin_id, recreate, ...rest] } diff --git a/examples/puzzles/singleton.rue b/examples/puzzles/singleton.rue index 5ea7fc20..abfef1ab 100644 --- a/examples/puzzles/singleton.rue +++ b/examples/puzzles/singleton.rue @@ -28,7 +28,7 @@ fn main( // Verify the amount and parent coin id // TODO: proof is LineageProof is hand optimized here, it should be done by the compiler let (is_lineage, parent_coin_id) = if unchecked_cast::( - (proof as (Any, (Any, Any))).rest.rest + (proof as (Any, (Any, Any))).rest.rest, ) { inline let proof = unchecked_cast::(proof); ( @@ -37,7 +37,7 @@ fn main( proof.parent_parent_coin_info, singleton_puzzle_hash(singleton, proof.parent_inner_puzzle_hash), proof.parent_amount, - ) + ), ) } else { inline let proof = unchecked_cast::(proof); @@ -47,7 +47,7 @@ fn main( proof.parent_parent_coin_info, singleton.launcher_puzzle_hash, proof.parent_amount, - ) + ), ) }; @@ -62,7 +62,7 @@ fn main( let conditions = morph_conditions(singleton, inner_puzzle(...inner_solution), false); - [assert_amount, assert_parent, ...conditions,] + [assert_amount, assert_parent, ...conditions] } inline fn singleton_puzzle_hash(singleton: Singleton, inner_puzzle_hash: Bytes32) -> Bytes32 { @@ -106,11 +106,11 @@ test fn tests() { let launcher_puzzle_hash = sha256("launcher_puzzle"); let launcher_id = coinid(parent_parent_coin_info, launcher_puzzle_hash, 0); - let singleton = Singleton { mod_hash: tree_hash(main), launcher_id, launcher_puzzle_hash, }; + let singleton = Singleton { mod_hash: tree_hash(main), launcher_id, launcher_puzzle_hash }; let inner_puzzle = fn(...solution: List) => solution; - let proof = EveProof { parent_parent_coin_info, parent_amount: 0, }; + let proof = EveProof { parent_parent_coin_info, parent_amount: 0 }; let p2_puzzle_hash = sha256("p2_puzzle_hash"); @@ -119,17 +119,14 @@ test fn tests() { inner_puzzle, proof, 1, - [CreateCoin { puzzle_hash: p2_puzzle_hash, amount: 1, }], + [CreateCoin { puzzle_hash: p2_puzzle_hash, amount: 1 }], ); assert tree_hash(conditions) == tree_hash( [ AssertMyAmount { amount: 1 }, AssertMyParentId { parent_id: launcher_id }, - CreateCoin { - puzzle_hash: singleton_puzzle_hash(singleton, p2_puzzle_hash), - amount: 1, - }, - ] + CreateCoin { puzzle_hash: singleton_puzzle_hash(singleton, p2_puzzle_hash), amount: 1 }, + ], ); } diff --git a/tests/builtins.rue b/tests/builtins.rue index 93915e01..14a7328a 100644 --- a/tests/builtins.rue +++ b/tests/builtins.rue @@ -4,10 +4,8 @@ test fn casting() { let noop = unchecked_cast::(...value: T) -> T>(1); assert noop(...42) == 42; - let first = unchecked_cast::(...value: (A, B)) -> A>(2); - let rest = unchecked_cast::(...value: (A, B)) -> B>(3); + let first = unchecked_cast::(...value: (A, B)) -> A>(2); + let rest = unchecked_cast::(...value: (A, B)) -> B>(3); assert first(...("hello", "world")) == "hello"; assert rest(...("hello", "world")) == "world"; } @@ -15,18 +13,18 @@ test fn casting() { test fn hash_functions() { assert sha256("hello") == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; assert sha256( - ...["hel", "lo"] + ...["hel", "lo"], ) == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; assert sha256_inline( - "hello" + "hello", ) == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; assert keccak256("hello") == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; assert keccak256( - ...["hel", "lo"] + ...["hel", "lo"], ) == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; assert keccak256_inline( - "hello" + "hello", ) == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; let parent_coin_id = sha256("parent_coin_id"); @@ -86,7 +84,7 @@ test fn g1_math() { assert pubkey_for_exp(unchecked_cast::(0)) == INFINITY_G1; assert pubkey_for_exp( - 0x0000000000000000000000000000000000000000000000000000000000000000 + 0x0000000000000000000000000000000000000000000000000000000000000000, ) == INFINITY_G1; assert pubkey_for_exp(sha256(nil)) == pk; diff --git a/tests/builtins.yaml b/tests/builtins.yaml index 098957be..f9882f8b 100644 --- a/tests/builtins.yaml +++ b/tests/builtins.yaml @@ -1,49 +1,49 @@ tests: - name: casting program: (a (i (= (q . "hello") (q . "hello")) (q 2 (i (= (a (q . 1) (q . 42)) (q . 42)) (q 2 (i (= (a (q . 2) (q "hello" . "world")) (q . "hello")) (q 2 (i (= (a (q . 3) (q "hello" . "world")) (q . "world")) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) - debug_program: (a (i (= (q . "hello") (q . "hello")) (q 2 (q 2 (i (= (a 2 (q . 42)) (q . 42)) (q 2 (q 2 (i (= (a 4 (c (q . "hello") (q . "world"))) (q . "hello")) (q 2 (i (= (a 6 (c (q . "hello") (q . "world"))) (q . "world")) (q) (q 8 (q . "assertion failed at builtins.rue:12:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:11:5"))) 1) (c (c (q . 2) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:5:5"))) 1) (c (q . 1) 1)) (q 8 (q . "assertion failed at builtins.rue:2:5"))) 1) + debug_program: (a (i (= (q . "hello") (q . "hello")) (q 2 (q 2 (i (= (a 2 (q . 42)) (q . 42)) (q 2 (q 2 (i (= (a 4 (c (q . "hello") (q . "world"))) (q . "hello")) (q 2 (i (= (a 6 (c (q . "hello") (q . "world"))) (q . "world")) (q) (q 8 (q . "assertion failed at builtins.rue:10:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:9:5"))) 1) (c (c (q . 2) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:5:5"))) 1) (c (q . 1) 1)) (q 8 (q . "assertion failed at builtins.rue:2:5"))) 1) output: () runtime_cost: 2017 byte_cost: 2268000 total_cost: 2270017 - name: hash_functions program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (q "hel" 27759)) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (q "hel" 27759)) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 9 21 29)) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8)) 1) (q 8)) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) - debug_program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 (concat (concat 9 21) 29))) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8 (q . "assertion failed at builtins.rue:38:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:37:5"))) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8 (q . "assertion failed at builtins.rue:28:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:25:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:24:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:20:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:17:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:16:5"))) 1) + debug_program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 (concat (concat 9 21) 29))) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8 (q . "assertion failed at builtins.rue:36:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:35:5"))) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8 (q . "assertion failed at builtins.rue:26:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:23:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:22:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:18:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:15:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:14:5"))) 1) output: () runtime_cost: 10577 byte_cost: 8328000 total_cost: 8338577 - name: byte_manipulation program: (a (i (a (c (c (q . 14) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 14) ()) (q "hello")) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (q "hel" 27759)) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 14) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hello") ())) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8 (q . "assertion failed at builtins.rue:48:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:47:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:46:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:44:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:43:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:42:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 14) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hello") ())) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8 (q . "assertion failed at builtins.rue:46:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:45:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:44:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:42:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:41:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:40:5"))) 1) output: () runtime_cost: 4460 byte_cost: 3720000 total_cost: 3724460 - name: arithmetic program: (a (i (a (c (c (q . 16) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 16) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (q 1 2)) ()) (q . 3)) (q 2 (i (a (c (c (q . 17) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 17) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (q 1 2)) ()) (q . -1)) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (q 2 3)) ()) (q . 6)) (q 2 (q 2 (i (= 4 (q . 3)) (q 2 (i (= 6 (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 3 . 1) 1)) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1) (q 8)) 1) (q 8)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 16) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) (c (q . 2) ()))) ()) (q . 3)) (q 2 (i (= (a (c (c (q . 17) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) (c (q . 2) ()))) ()) (- () (q . 1))) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 2) (c (q . 3) ()))) ()) (q . 6)) (q 2 (q 2 (i (= (f 2) (q . 3)) (q 2 (i (= (r 2) (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8 (q . "assertion failed at builtins.rue:68:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:66:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:65:5"))) 1) (c (divmod (q . 10) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:62:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:61:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:60:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:58:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:57:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:56:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:54:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:53:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:52:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 16) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) (c (q . 2) ()))) ()) (q . 3)) (q 2 (i (= (a (c (c (q . 17) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) (c (q . 2) ()))) ()) (- () (q . 1))) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 2) (c (q . 3) ()))) ()) (q . 6)) (q 2 (q 2 (i (= (f 2) (q . 3)) (q 2 (i (= (r 2) (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8 (q . "assertion failed at builtins.rue:66:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:64:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:63:5"))) 1) (c (divmod (q . 10) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:60:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:59:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:58:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:56:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:55:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:54:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:52:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:51:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:50:5"))) 1) output: () runtime_cost: 28961 byte_cost: 6768000 total_cost: 6796961 - name: boolean_logic program: (a (i (a (c (c (q . 33) ()) ()) ()) (q 8) (q 2 (i (a (c (c (q . 33) ()) (q ())) ()) (q 8) (q 2 (i (= (a (c (c (q . 33) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (q () 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (a (c (c (q . 34) ()) (q ())) ()) (q 8) (q 2 (i (= (a (c (c (q . 34) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (q 1 1)) ()) (q . 1)) (q) (q 8)) 1) (q 8)) 1)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 33) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (c () (c (q . 1) ()))) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) (c (q . 1) ()))) ()) (q . 1)) (q) (q 8 (q . "assertion failed at builtins.rue:80:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:79:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:78:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:77:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:75:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:74:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:73:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:72:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 33) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (c () (c (q . 1) ()))) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) (c (q . 1) ()))) ()) (q . 1)) (q) (q 8 (q . "assertion failed at builtins.rue:78:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:77:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:76:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:75:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:73:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:72:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:71:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:70:5"))) 1) output: () runtime_cost: 9752 byte_cost: 4668000 total_cost: 4677752 - name: g1_math program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5) 1)) (c (point_add) ())) - debug_program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:105:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:104:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:103:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:102:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:101:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:100:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:99:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:97:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:96:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:95:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:94:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:93:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:91:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:88:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:87:5"))) 1) (c (c (q . 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b) (q . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5)) 1)) (c (point_add) ())) + debug_program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:103:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:102:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:101:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:100:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:99:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:98:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:97:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:95:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:94:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:93:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:92:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:91:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:89:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:86:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:85:5"))) 1) (c (c (q . 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b) (q . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5)) 1)) (c (point_add) ())) output: () runtime_cost: 28164410 byte_cost: 10956000 total_cost: 39120410 - name: g2_math program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063 . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a) 1)) (c (g2_add) ())) - debug_program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:124:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:123:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:122:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:121:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:120:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:119:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:118:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:116:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:115:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:114:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:113:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:112:5"))) 1) (c (c (q . 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063) (q . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a)) 1)) (c (g2_add) ())) + debug_program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:122:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:121:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:120:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:119:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:118:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:117:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:116:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:114:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:113:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:112:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:111:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:110:5"))) 1) (c (c (q . 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063) (q . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a)) 1)) (c (g2_add) ())) output: () runtime_cost: 34216780 byte_cost: 10596000 diff --git a/tests/imports/duplicate_imports.rue b/tests/imports/duplicate_imports.rue index 2cf067f3..0f966633 100644 --- a/tests/imports/duplicate_imports.rue +++ b/tests/imports/duplicate_imports.rue @@ -1,3 +1,5 @@ +import a::thing; + mod a { export fn thing() -> Int { 42 @@ -7,8 +9,6 @@ mod a { export thing; } -import a::thing; - fn main() -> Int { thing() } diff --git a/tests/imports/duplicate_imports.yaml b/tests/imports/duplicate_imports.yaml index 9e91f18d..053f1792 100644 --- a/tests/imports/duplicate_imports.yaml +++ b/tests/imports/duplicate_imports.yaml @@ -5,5 +5,5 @@ runtime_cost: 20 byte_cost: 36000 total_cost: 36020 diagnostics: -- Unused import `thing` at duplicate_imports.rue:6:12 -- Unused import `thing` at duplicate_imports.rue:7:12 +- Unused import `thing` at duplicate_imports.rue:8:12 +- Unused import `thing` at duplicate_imports.rue:9:12 diff --git a/tests/imports/glob_export.rue b/tests/imports/glob_export.rue index 611e3b0a..63258aa1 100644 --- a/tests/imports/glob_export.rue +++ b/tests/imports/glob_export.rue @@ -1,3 +1,5 @@ +import utils::*; + mod utils { mod inner_utils { export fn inner_hello() -> String { @@ -12,8 +14,6 @@ mod utils { } } -import utils::*; - fn main() -> String { inner_hello() + "! " + thing() + "!" } diff --git a/tests/imports/import_order.rue b/tests/imports/import_order.rue index 41be92c7..a939f5f9 100644 --- a/tests/imports/import_order.rue +++ b/tests/imports/import_order.rue @@ -1,3 +1,7 @@ +import a::hello_world_a; + +import b::hello_world_b; + mod a { import b::hello; @@ -22,10 +26,6 @@ mod b { } } -import a::hello_world_a; - -import b::hello_world_b; - test fn a_entrypoint() -> String { hello_world_a() } diff --git a/tests/imports/multi_namespace.rue b/tests/imports/multi_namespace.rue index 5f00fb10..d0511e78 100644 --- a/tests/imports/multi_namespace.rue +++ b/tests/imports/multi_namespace.rue @@ -1,11 +1,11 @@ +import module::X; + mod module { const X: X = 42; type X = 42; export X; } -import module::X; - fn main() -> X { X } diff --git a/tests/imports/symbol_import.rue b/tests/imports/symbol_import.rue index e20081ca..f62d96e4 100644 --- a/tests/imports/symbol_import.rue +++ b/tests/imports/symbol_import.rue @@ -1,11 +1,11 @@ +import a::hello; + mod a { export fn hello() -> String { "Hello, world!" } } -import a::hello; - fn main() -> String { hello() } diff --git a/tests/imports/type_import.rue b/tests/imports/type_import.rue index 4f0f41f5..b248d573 100644 --- a/tests/imports/type_import.rue +++ b/tests/imports/type_import.rue @@ -1,9 +1,9 @@ +import a::Hello; + mod a { export type Hello = String; } -import a::Hello; - fn main() -> Hello { "Hello, world!" } diff --git a/tests/imports/unresolved_imports.rue b/tests/imports/unresolved_imports.rue index 053bd37f..c441bd1e 100644 --- a/tests/imports/unresolved_imports.rue +++ b/tests/imports/unresolved_imports.rue @@ -1,7 +1,7 @@ -mod module {} - import module::missing; import module::inner::missing; import module::inner::*; + +mod module {} diff --git a/tests/imports/unresolved_imports.yaml b/tests/imports/unresolved_imports.yaml index e42294b1..6cfcccf1 100644 --- a/tests/imports/unresolved_imports.yaml +++ b/tests/imports/unresolved_imports.yaml @@ -1,4 +1,4 @@ diagnostics: +- Undeclared symbol `inner` at unresolved_imports.rue:3:16 - Undeclared symbol `inner` at unresolved_imports.rue:5:16 -- Undeclared symbol `inner` at unresolved_imports.rue:7:16 -- Unresolved import `missing` at unresolved_imports.rue:3:16 +- Unresolved import `missing` at unresolved_imports.rue:1:16 diff --git a/tests/std.rue b/tests/std.rue index 4b952c4a..ed7deee9 100644 --- a/tests/std.rue +++ b/tests/std.rue @@ -10,15 +10,15 @@ test fn condition_type_guards() { assert AggSigAmount { message: nil, public_key: INFINITY_G1 } as Condition is AggSigAmount; assert AggSigPuzzleAmount { message: nil, - public_key: INFINITY_G1 + public_key: INFINITY_G1, } as Condition is AggSigPuzzleAmount; assert AggSigParentAmount { message: nil, - public_key: INFINITY_G1 + public_key: INFINITY_G1, } as Condition is AggSigParentAmount; assert AggSigParentPuzzle { message: nil, - public_key: INFINITY_G1 + public_key: INFINITY_G1, } as Condition is AggSigParentPuzzle; assert AggSigUnsafe { message: nil, public_key: INFINITY_G1 } as Condition is AggSigUnsafe; assert AggSigMe { message: nil, public_key: INFINITY_G1 } as Condition is AggSigMe; @@ -30,7 +30,7 @@ test fn condition_type_guards() { assert AssertPuzzleAnnouncement { id: id } as Condition is AssertPuzzleAnnouncement; assert AssertConcurrentSpend { coin_id: coin_id } as Condition is AssertConcurrentSpend; assert AssertConcurrentPuzzle { - puzzle_hash: puzzle_hash + puzzle_hash: puzzle_hash, } as Condition is AssertConcurrentPuzzle; assert SendMessage { mode: 42, message: nil, receiver: [42] } as Condition is SendMessage; assert ReceiveMessage { mode: 42, message: nil, sender: [42] } as Condition is ReceiveMessage; @@ -65,7 +65,7 @@ test fn currying() { test fn recursion() { check_each( - [nil, "Hello, world!", (100, 200), [1, 2, 3], fn(a: Int, b: Int) => a + b, check_each,] + [nil, "Hello, world!", (100, 200), [1, 2, 3], fn(a: Int, b: Int) => a + b, check_each], ); } diff --git a/tests/stress/many_params_binary_tree.rue b/tests/stress/many_params_binary_tree.rue index 8ef756a3..bfe9fbc8 100644 --- a/tests/stress/many_params_binary_tree.rue +++ b/tests/stress/many_params_binary_tree.rue @@ -259,7 +259,7 @@ test fn call_binary_tree() { 257, 258, 259, - 260 + 260, ); } diff --git a/tests/stress/many_params_non_overflow.rue b/tests/stress/many_params_non_overflow.rue index 8b03f244..6703503d 100644 --- a/tests/stress/many_params_non_overflow.rue +++ b/tests/stress/many_params_non_overflow.rue @@ -29,7 +29,7 @@ test fn call_binary_tree() { 23, 24, 25, - 26 + 26, ); } @@ -60,7 +60,7 @@ test fn call_extern() { 23, 24, 25, - 26 + 26, ); } diff --git a/tests/symbols/super_errors.rue b/tests/symbols/super_errors.rue index 168d8f46..834b8d13 100644 --- a/tests/symbols/super_errors.rue +++ b/tests/symbols/super_errors.rue @@ -1,9 +1,9 @@ -mod a { - import super::a; -} - import super::x; import super; import super::super; + +mod a { + import super::a; +} diff --git a/tests/symbols/super_errors.yaml b/tests/symbols/super_errors.yaml index ba55a7a3..72c49a97 100644 --- a/tests/symbols/super_errors.yaml +++ b/tests/symbols/super_errors.yaml @@ -1,10 +1,10 @@ diagnostics: -- Unresolved `super`, there is no parent module at super_errors.rue:2:12 +- Unresolved `super`, there is no parent module at super_errors.rue:1:8 +- Cannot end path in `super` at super_errors.rue:3:8 - Unresolved `super`, there is no parent module at super_errors.rue:5:8 -- Cannot end path in `super` at super_errors.rue:7:8 -- Unresolved `super`, there is no parent module at super_errors.rue:9:8 -- Cannot end path in `super` at super_errors.rue:9:15 -- Unresolved import `x` at super_errors.rue:5:15 -- Unresolved import `super` at super_errors.rue:7:8 -- Unresolved import `super` at super_errors.rue:9:15 -- Unused import `a` at super_errors.rue:2:19 +- Cannot end path in `super` at super_errors.rue:5:15 +- Unresolved `super`, there is no parent module at super_errors.rue:8:12 +- Unresolved import `x` at super_errors.rue:1:15 +- Unresolved import `super` at super_errors.rue:3:8 +- Unresolved import `super` at super_errors.rue:5:15 +- Unused import `a` at super_errors.rue:8:19 diff --git a/tests/types/struct_error.rue b/tests/types/struct_error.rue index 24057944..ae70f729 100644 --- a/tests/types/struct_error.rue +++ b/tests/types/struct_error.rue @@ -4,5 +4,5 @@ struct Point { } test fn struct_error() -> Point { - Point { x: "1", y: 2, } + Point { x: "1", y: 2 } } diff --git a/tests/warnings/unused_imports.rue b/tests/warnings/unused_imports.rue index b5b95285..e44e272a 100644 --- a/tests/warnings/unused_imports.rue +++ b/tests/warnings/unused_imports.rue @@ -1,3 +1,25 @@ +import single::a; + +import glob::*; + +import shadow_1::c; + +import shadow_1::*; + +import shadow_2::*; + +import shadow_2::d; + +import private::*; + +import empty::*; + +import deeply::nested; + +import nested::module; + +import module::used; + mod single { export fn a() -> Int { 42 @@ -30,22 +52,6 @@ mod private { mod empty {} -import single::a; - -import glob::*; - -import shadow_1::c; - -import shadow_1::*; - -import shadow_2::*; - -import shadow_2::d; - -import private::*; - -import empty::*; - mod deeply { export mod nested { export mod module { @@ -56,12 +62,6 @@ mod deeply { } } -import deeply::nested; - -import nested::module; - -import module::used; - fn main() -> Int { used() } diff --git a/tests/warnings/unused_imports.yaml b/tests/warnings/unused_imports.yaml index c0c7812d..a974ad3d 100644 --- a/tests/warnings/unused_imports.yaml +++ b/tests/warnings/unused_imports.yaml @@ -5,16 +5,16 @@ runtime_cost: 20 byte_cost: 36000 total_cost: 36020 diagnostics: -- Unused import `d` at unused_imports.rue:43:18 -- Unused import `*` at unused_imports.rue:39:18 -- Unused import `*` at unused_imports.rue:45:17 -- Unused import `*` at unused_imports.rue:47:15 -- Unused import `a` at unused_imports.rue:33:16 -- Unused import `*` at unused_imports.rue:35:14 -- Unused import `c` at unused_imports.rue:37:18 -- Unused import `*` at unused_imports.rue:41:18 -- Unused function `a` at unused_imports.rue:2:15 -- Unused function `b` at unused_imports.rue:8:15 -- Unused function `c` at unused_imports.rue:14:15 -- Unused function `d` at unused_imports.rue:20:15 -- Unused function `private` at unused_imports.rue:26:8 +- Unused import `d` at unused_imports.rue:11:18 +- Unused import `*` at unused_imports.rue:7:18 +- Unused import `*` at unused_imports.rue:13:17 +- Unused import `*` at unused_imports.rue:15:15 +- Unused import `a` at unused_imports.rue:1:16 +- Unused import `*` at unused_imports.rue:3:14 +- Unused import `c` at unused_imports.rue:5:18 +- Unused import `*` at unused_imports.rue:9:18 +- Unused function `a` at unused_imports.rue:24:15 +- Unused function `b` at unused_imports.rue:30:15 +- Unused function `c` at unused_imports.rue:36:15 +- Unused function `d` at unused_imports.rue:42:15 +- Unused function `private` at unused_imports.rue:48:8 From bb3c6e1fb17094c86dc6c840d7a3c5c3c51b1358 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 16:13:17 -0400 Subject: [PATCH 03/21] More fixes --- .../src/std/conditions/message_flags.rue | 6 -- .../src/std/conditions/opcodes.rue | 34 --------- .../rue-compiler/src/std/conditions/types.rue | 70 +++++++++---------- .../rue-compiler/src/std/curry_tree_hash.rue | 3 +- crates/rue-compiler/src/std/utils.rue | 3 +- crates/rue-formatter/src/format.rs | 36 +++++++++- crates/rue-formatter/src/tests.rs | 35 ++++++++++ tests/blocks/if_stmt.rue | 3 +- 8 files changed, 111 insertions(+), 79 deletions(-) diff --git a/crates/rue-compiler/src/std/conditions/message_flags.rue b/crates/rue-compiler/src/std/conditions/message_flags.rue index 8c014d99..db5f501f 100644 --- a/crates/rue-compiler/src/std/conditions/message_flags.rue +++ b/crates/rue-compiler/src/std/conditions/message_flags.rue @@ -1,15 +1,9 @@ export inline const SENDER_COIN: 0b111_000 = 0b111_000; - export inline const SENDER_PARENT: 0b100_000 = 0b100_000; - export inline const SENDER_PUZZLE: 0b010_000 = 0b010_000; - export inline const SENDER_AMOUNT: 0b001_000 = 0b001_000; export inline const RECEIVER_COIN: 0b000_111 = 0b000_111; - export inline const RECEIVER_PARENT: 0b000_100 = 0b000_100; - export inline const RECEIVER_PUZZLE: 0b000_010 = 0b000_010; - export inline const RECEIVER_AMOUNT: 0b000_001 = 0b000_001; diff --git a/crates/rue-compiler/src/std/conditions/opcodes.rue b/crates/rue-compiler/src/std/conditions/opcodes.rue index 4d87d14a..309ab18f 100644 --- a/crates/rue-compiler/src/std/conditions/opcodes.rue +++ b/crates/rue-compiler/src/std/conditions/opcodes.rue @@ -1,69 +1,35 @@ export inline const REMARK: 1 = 1; - export inline const AGG_SIG_PARENT: 43 = 43; - export inline const AGG_SIG_PUZZLE: 44 = 44; - export inline const AGG_SIG_AMOUNT: 45 = 45; - export inline const AGG_SIG_PUZZLE_AMOUNT: 46 = 46; - export inline const AGG_SIG_PARENT_AMOUNT: 47 = 47; - export inline const AGG_SIG_PARENT_PUZZLE: 48 = 48; - export inline const AGG_SIG_UNSAFE: 49 = 49; - export inline const AGG_SIG_ME: 50 = 50; - export inline const CREATE_COIN: 51 = 51; - export inline const RESERVE_FEE: 52 = 52; - export inline const CREATE_COIN_ANNOUNCEMENT: 60 = 60; - export inline const ASSERT_COIN_ANNOUNCEMENT: 61 = 61; - export inline const CREATE_PUZZLE_ANNOUNCEMENT: 62 = 62; - export inline const ASSERT_PUZZLE_ANNOUNCEMENT: 63 = 63; - export inline const ASSERT_CONCURRENT_SPEND: 64 = 64; - export inline const ASSERT_CONCURRENT_PUZZLE: 65 = 65; - export inline const SEND_MESSAGE: 66 = 66; - export inline const RECEIVE_MESSAGE: 67 = 67; - export inline const ASSERT_MY_COIN_ID: 70 = 70; - export inline const ASSERT_MY_PARENT_ID: 71 = 71; - export inline const ASSERT_MY_PUZZLE_HASH: 72 = 72; - export inline const ASSERT_MY_AMOUNT: 73 = 73; - export inline const ASSERT_MY_BIRTH_SECONDS: 74 = 74; - export inline const ASSERT_MY_BIRTH_HEIGHT: 75 = 75; - export inline const ASSERT_EPHEMERAL: 76 = 76; - export inline const ASSERT_SECONDS_RELATIVE: 80 = 80; - export inline const ASSERT_SECONDS_ABSOLUTE: 81 = 81; - export inline const ASSERT_HEIGHT_RELATIVE: 82 = 82; - export inline const ASSERT_HEIGHT_ABSOLUTE: 83 = 83; - export inline const ASSERT_BEFORE_SECONDS_RELATIVE: 84 = 84; - export inline const ASSERT_BEFORE_SECONDS_ABSOLUTE: 85 = 85; - export inline const ASSERT_BEFORE_HEIGHT_RELATIVE: 86 = 86; - export inline const ASSERT_BEFORE_HEIGHT_ABSOLUTE: 87 = 87; - export inline const SOFTFORK: 90 = 90; diff --git a/crates/rue-compiler/src/std/conditions/types.rue b/crates/rue-compiler/src/std/conditions/types.rue index e9fd5bdb..7e6d1598 100644 --- a/crates/rue-compiler/src/std/conditions/types.rue +++ b/crates/rue-compiler/src/std/conditions/types.rue @@ -193,38 +193,38 @@ export struct Softfork { ...args: List, } -export type Condition = Remark | - AggSigParent | - AggSigPuzzle | - AggSigAmount | - AggSigPuzzleAmount | - AggSigParentAmount | - AggSigParentPuzzle | - AggSigUnsafe | - AggSigMe | - CreateCoin | - ReserveFee | - CreateCoinAnnouncement | - AssertCoinAnnouncement | - CreatePuzzleAnnouncement | - AssertPuzzleAnnouncement | - AssertConcurrentSpend | - AssertConcurrentPuzzle | - SendMessage | - ReceiveMessage | - AssertMyCoinId | - AssertMyParentId | - AssertMyPuzzleHash | - AssertMyAmount | - AssertMyBirthSeconds | - AssertMyBirthHeight | - AssertEphemeral | - AssertSecondsRelative | - AssertSecondsAbsolute | - AssertHeightRelative | - AssertHeightAbsolute | - AssertBeforeSecondsRelative | - AssertBeforeSecondsAbsolute | - AssertBeforeHeightRelative | - AssertBeforeHeightAbsolute | - Softfork; +export type Condition = Remark + | AggSigParent + | AggSigPuzzle + | AggSigAmount + | AggSigPuzzleAmount + | AggSigParentAmount + | AggSigParentPuzzle + | AggSigUnsafe + | AggSigMe + | CreateCoin + | ReserveFee + | CreateCoinAnnouncement + | AssertCoinAnnouncement + | CreatePuzzleAnnouncement + | AssertPuzzleAnnouncement + | AssertConcurrentSpend + | AssertConcurrentPuzzle + | SendMessage + | ReceiveMessage + | AssertMyCoinId + | AssertMyParentId + | AssertMyPuzzleHash + | AssertMyAmount + | AssertMyBirthSeconds + | AssertMyBirthHeight + | AssertEphemeral + | AssertSecondsRelative + | AssertSecondsAbsolute + | AssertHeightRelative + | AssertHeightAbsolute + | AssertBeforeSecondsRelative + | AssertBeforeSecondsAbsolute + | AssertBeforeHeightRelative + | AssertBeforeHeightAbsolute + | Softfork; diff --git a/crates/rue-compiler/src/std/curry_tree_hash.rue b/crates/rue-compiler/src/std/curry_tree_hash.rue index 799e4daa..2e65df81 100644 --- a/crates/rue-compiler/src/std/curry_tree_hash.rue +++ b/crates/rue-compiler/src/std/curry_tree_hash.rue @@ -27,7 +27,8 @@ fn update_hash_with_parameter(parameter_hash: Bytes32, environment_hash: Bytes32 fn curried_params_hash(parameters: List) -> Bytes32 { if parameters is nil { return tree_hash_atom(1 as Bytes); - } update_hash_with_parameter(parameters.first, curried_params_hash(parameters.rest)) + } + update_hash_with_parameter(parameters.first, curried_params_hash(parameters.rest)) } export fn curry_tree_hash(mod_hash: Bytes32, parameters: List) -> Bytes32 { diff --git a/crates/rue-compiler/src/std/utils.rue b/crates/rue-compiler/src/std/utils.rue index 239a346e..67d0cff5 100644 --- a/crates/rue-compiler/src/std/utils.rue +++ b/crates/rue-compiler/src/std/utils.rue @@ -1,7 +1,8 @@ export fn merge_list(a: List, b: List) -> List { if a is (T, List) { return [a.first, ...merge_list(a.rest, b)]; - } b + } + b } export fn deep_equal(a: Any, b: Any) -> Bool { diff --git a/crates/rue-formatter/src/format.rs b/crates/rue-formatter/src/format.rs index 5ca18d61..410196f3 100644 --- a/crates/rue-formatter/src/format.rs +++ b/crates/rue-formatter/src/format.rs @@ -35,6 +35,7 @@ struct Layout { prefix_operators: HashSet, attached_openers: HashSet, item_ends: HashSet, + block_item_ends: HashSet, groups: HashMap, binary_operators: HashMap>, document_items: Vec, @@ -416,6 +417,9 @@ impl Formatter<'_> { if self.layout.item_ends.contains(&left.start) { return Separator::Empty; } + if self.layout.block_item_ends.contains(&left.start) { + return Separator::Hard; + } if left.kind == T![;] { return Separator::Hard; @@ -646,6 +650,17 @@ impl Layout { item_ends.insert(item_end); } } + let mut block_item_ends = HashSet::new(); + for block in root + .descendants() + .filter(|node| node.kind() == SyntaxKind::Block) + { + for item in block.children() { + if let Some(token) = significant_tokens(&item).last() { + block_item_ends.insert(usize::from(token.text_range().start())); + } + } + } let token_indices: HashMap = stream .tokens @@ -727,9 +742,13 @@ impl Layout { .entry(start) .and_modify(|current: &mut usize| *current = (*current).max(end)) .or_insert(end); + let mut operators = Vec::new(); if node.kind() == SyntaxKind::BinaryExpr { - let mut operators = Vec::new(); collect_binary_operator_starts(&node, &mut operators); + } else { + collect_union_operator_starts(&node, &mut operators); + } + if !operators.is_empty() { binary_operators.insert( start, operators @@ -816,6 +835,7 @@ impl Layout { prefix_operators, attached_openers, item_ends, + block_item_ends, groups, binary_operators, document_items, @@ -838,6 +858,20 @@ fn collect_binary_operator_starts(node: &SyntaxNode, operators: &mut Vec) } } +fn collect_union_operator_starts(node: &SyntaxNode, operators: &mut Vec) { + for element in node.children_with_tokens() { + match element { + rowan::NodeOrToken::Node(child) if child.kind() == SyntaxKind::UnionType => { + collect_union_operator_starts(&child, operators); + } + rowan::NodeOrToken::Token(token) if token.kind() == T![|] => { + operators.push(usize::from(token.text_range().start())); + } + _ => {} + } + } +} + fn significant_tokens(node: &SyntaxNode) -> impl Iterator + '_ { node.descendants_with_tokens() .filter_map(rowan::NodeOrToken::into_token) diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index cde81815..c777fe46 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -289,6 +289,26 @@ fn generic_trailing_commas_follow_layout() { assert_eq!(format_source(&output, &options).unwrap(), output); } +#[test] +fn union_type_operators_lead_continuation_lines() { + let options = FormatOptions { + max_width: 32, + ..FormatOptions::default() + }; + let output = format_source( + "type Condition = FirstVariant | SecondVariant | ThirdVariant;", + &options, + ) + .unwrap(); + expect![[r#" + type Condition = FirstVariant + | SecondVariant + | ThirdVariant; + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + #[test] fn imports_are_hoisted_grouped_and_sorted() { check( @@ -335,3 +355,18 @@ fn compact_items_are_grouped() { "#]], ); } + +#[test] +fn block_items_separate_implicit_return_expression() { + check( + "fn recurse(items: List) -> Int { if items is nil { return 0; } recurse(items.rest) }", + expect![[r#" + fn recurse(items: List) -> Int { + if items is nil { + return 0; + } + recurse(items.rest) + } + "#]], + ); +} diff --git a/tests/blocks/if_stmt.rue b/tests/blocks/if_stmt.rue index 1a6f2283..b74fd9ef 100644 --- a/tests/blocks/if_stmt.rue +++ b/tests/blocks/if_stmt.rue @@ -1,5 +1,6 @@ fn main() -> Int { if 42 > 10 { return 123; - } 100 + } + 100 } From 2b275104af37cf83b2545a3ec1db5c5e79816f74 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 16:16:04 -0400 Subject: [PATCH 04/21] prelude fmt --- crates/rue-compiler/src/std/prelude.rue | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/rue-compiler/src/std/prelude.rue b/crates/rue-compiler/src/std/prelude.rue index 5d779246..445f7c6f 100644 --- a/crates/rue-compiler/src/std/prelude.rue +++ b/crates/rue-compiler/src/std/prelude.rue @@ -1,16 +1,10 @@ -export utils::*; - -export tree_hash::*; - -export curry_tree_hash::*; - export conditions::message_flags::*; - export conditions::opcodes::*; - export conditions::types::*; - +export curry_tree_hash::*; export merkle::*; +export tree_hash::*; +export utils::*; export mod std { export conditions; From 225677500f6dc26a2f8fc6347a3af50440a6e96c Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 16:20:51 -0400 Subject: [PATCH 05/21] Some more fixes --- crates/rue-formatter/src/format.rs | 26 ++++++++++++-- crates/rue-formatter/src/tests.rs | 55 ++++++++++++++++++++++++++++++ examples/puzzles/cat.rue | 3 +- examples/puzzles/cat.yaml | 2 +- 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/crates/rue-formatter/src/format.rs b/crates/rue-formatter/src/format.rs index 410196f3..3c9ccd11 100644 --- a/crates/rue-formatter/src/format.rs +++ b/crates/rue-formatter/src/format.rs @@ -160,11 +160,33 @@ impl Formatter<'_> { let mut index = start; while index < end { + let mut emitted_separator = false; let atom_end = if let Some(&group_end) = self.layout.groups.get(&index) && group_end < end && !(index == start && group_end + 1 == end) { - docs.push(self.grouped_span(index, group_end + 1)?); + let next = group_end + 1; + let gap = &self.stream.gaps[next]; + if self.layout.binary_operators.contains_key(&index) + && self + .stream + .tokens + .get(next) + .is_some_and(|token| self.layout.block_openers.contains(&token.start)) + && gap.comments.is_empty() + && gap.newlines <= 1 + { + docs.push( + Doc::concat([ + self.binary_span(index, next)?, + Doc::if_break(Doc::hard_line(), Doc::space()), + ]) + .group(), + ); + emitted_separator = true; + } else { + docs.push(self.grouped_span(index, next)?); + } group_end } else if let Some(&close) = self.layout.pairs.get(&index) { if close >= end { @@ -180,7 +202,7 @@ impl Formatter<'_> { }; index = atom_end + 1; - if index < end { + if index < end && !emitted_separator { let separator = self.separator(atom_end, index); docs.push(self.gap_doc(&self.stream.gaps[index], separator)); } diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index c777fe46..5438d93c 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -236,6 +236,61 @@ fn binary_operators_inside_if_are_not_chain_members() { ); } +#[test] +fn wrapped_condition_places_open_brace_on_own_line() { + let options = FormatOptions { + max_width: 30, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){if first_condition&&second_condition{return 1;}0}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + if first_condition + && second_condition + { + return 1; + } + 0 + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn wrapped_function_headers_place_open_brace_on_own_line() { + let options = FormatOptions { + max_width: 34, + ..FormatOptions::default() + }; + let output = format_source( + "fn parameters(first: FirstType, second: SecondType){0}\nfn result()->FirstVariant|SecondVariant|ThirdVariant{0}", + &options, + ) + .unwrap(); + expect![[r#" + fn parameters( + first: FirstType, + second: SecondType, + ) { + 0 + } + + fn result() -> FirstVariant + | SecondVariant + | ThirdVariant + { + 0 + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + #[test] fn comparison_wraps_rhs_inside_logical_chain() { let options = FormatOptions { diff --git a/examples/puzzles/cat.rue b/examples/puzzles/cat.rue index ca352586..ae58db79 100644 --- a/examples/puzzles/cat.rue +++ b/examples/puzzles/cat.rue @@ -229,7 +229,8 @@ fn morph_conditions( && ( (condition.message.length == 33) & (substr(condition.message, 0, 1) == RING_MORPH_BYTE) - ) { + ) + { raise "Created coin announcements must not have the ring morph byte"; } diff --git a/examples/puzzles/cat.yaml b/examples/puzzles/cat.yaml index 964085c9..c5fa0c6d 100644 --- a/examples/puzzles/cat.yaml +++ b/examples/puzzles/cat.yaml @@ -1,5 +1,5 @@ program: (a (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 27 ())) (a (i 57 (q 2 (i (= (a 39 (c 39 89)) 95) (q 2 -9 (c -9 (c (a 89 (c (c (c (a 39 (c 39 -65)) 19) (c 27 3071)) (c 13 (c 767 (c 24575 (c 2 (c -71 ()))))))) 2))) (q 8)) 1) (q 2 (i (all 13 (not 24575)) (q . 2) (q 8)) 1)) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a 19 (c 19 (c 767 (c 6143 ()))))) ())) (c (c (q . 61) (c (sha256 (coinid 5119 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 17) (sha256 (q . 2) (a 91 (c 91 (c 41 (c (sha256 (q . 1) 89) (c 11263 ()))))) (sha256 (q . 1))))) 23551) (q . -53) (a 19 (c 19 (c 13 (c (+ 6143 (- 11775 20) 12287) ()))))) ())) 8)) 1)) (c (c (a 21 (c (c 21 45) (c (a 47 95) (c 4 ())))) (a (i -65 (q 2 (i (= 1279 (coinid 319 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 8) (sha256 (q . 2) (a 45 (c 45 (c 20 (c (sha256 (q . 1) 44) (c 703 ()))))) (sha256 (q . 1))))) 1471)) (q 1 . 1) (q)) 1) (q)) 1)) 1)) (c (c (c 5 (c (sha256 (q . 1) 5) (c 11 ()))) (coinid 639 1407 2943)) 1)) (c (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (c (q 2 (i 5 (q 2 (i (a (i (= 17 (q . 51)) (q 2 (i 41 (q) (q 1 . 1)) 1) (q)) 1) (q 2 (q . 2) (c (a 4 (c 2 (c 13 (c 11 (c -71 (c 377 ())))))) 1)) (q 2 (q 4 (c 10 8) (c (+ 20 14) 28)) (c (c (a 4 (c 2 (c 13 7))) (a (i (= 17 (q . 51)) (q 4 (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 19) (sha256 (q . 2) (a 6 (c 6 (c 43 (c (sha256 (q . 1) 91) (c 41 ()))))) (sha256 (q . 1))))) 57)) 89) (q 2 (i (a (i (= 17 (q . 60)) (q 2 (i (all (= (strlen 41) (q . 33)) (= (substr 41 () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8) (q 4 9 ())) 1)) 1)) 1))) 1) (q 4 () (c () 15))) 1) (c (q 2 (i 3 (q 11 (q . 2) (sha256 (q . 260)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 5) (sha256 (q . 2) (a 2 (c 2 7)) (sha256 (q . 1))))) (q 11 (q . 257))) 1) (q 2 (i 5 (q 4 9 (a 2 (c 2 (c 13 7)))) (q . 7)) 1)))) 1)) -debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 351 ())) 2) (c (a (i (not (not (l (r (r 39))))) (q 2 (i (= (a 287 (c 287 (f (r (r 39))))) 383) (q 2 (q 2 1727 (c 1727 (c 2 5))) (c (a (f (r (r 39))) (c -9 (c 87 (c 3071 (c 0x017fff (c 2 (c (f (r (r (r 39)))) ()))))))) 1)) (q 8 (q . "assertion failed at cat.rue:149:9"))) 1) (q 2 (i (all 87 (= 0x017fff ())) (q . 2) (q 8 (q . "assertion failed at cat.rue:167:9"))) 1)) 1) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a -113 (c -113 (c 3071 (c 24575 ()))))) ())) (c (c (q . 61) (c (sha256 (concat (concat 91 (q . -53)) (a -113 (c -113 (c 87 (c 2 ())))))) ())) (f 19))) 1)) (c (+ (+ 12287 2) 24575) 1)) (c (- (f (r (r 1535))) (f (r 4))) 1)) (c (c (a 25 (c (c 25 (c 37 (c 45 (c 93 125)))) (c (a 47 95) (c 4 ())))) (c (a (i (l -65) (q 2 (i (= (f 767) (coinid (f -65) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r -65)) ())))))) (f (r (r -65))))) (q 1 . 1) (q)) 1) (q)) 1) (c (coinid (f 1535) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r 1535)) ())))))) (f (r (r 1535)))) (c (c 14 4) (c 10 767))))) 1)) (c (c (c 5 (c (sha256 (concat (q . 1) 5)) (c 11 ()))) (c (coinid (f 383) (f (r 383)) (f (r (r 383)))) (a 8 (c 8 23)))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 5)) (q 4 () (c () 15)) (q 2 (i (a (i (= (f (f 5)) (q . 51)) (q 2 (i (= (f (r (f 5))) ()) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (q 4 (f 2) (c (f (r 2)) (r (r 2)))) (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 (c (f (r (r (r (f 5))))) (c (f (r (r (r (r (f 5)))))) ())))))) 1)) (q 2 (q 4 (c (f 6) (f 4)) (c (+ (f (r 4)) (r 6)) (r (r 4)))) (c (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 15)))) (a (i (= (f (f 5)) (q . 51)) (q 2 (q 4 2 (f (r (r (f 11))))) (c (c (q . 51) (c (a 22 (c (c 10 (c 46 62)) (c (f 11) (c (f (r 11)) (c (sha256 (concat (q . 1) (f (r (r 11))))) (c (f (r (f 5))) ())))))) (c (f (r (r (f 5)))) (r (r (r (f 5))))))) 1)) (q 2 (i (a (i (= (f (f 5)) (q . 60)) (q 2 (i (all (= (strlen (f (r (f 5)))) (q . 33)) (= (substr (f (r (f 5))) () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8 (q . "raise called at cat.rue:233:13") (q . "Created coin announcements must not have the ring morph byte")) (q 4 (f 5) ())) 1)) 1)) 1))) 1)) 1)) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (q 2 (i (l 5) (q 4 (f 5) (a 2 (c 2 (c (r 5) 7)))) (q . 7)) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) +debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 351 ())) 2) (c (a (i (not (not (l (r (r 39))))) (q 2 (i (= (a 287 (c 287 (f (r (r 39))))) 383) (q 2 (q 2 1727 (c 1727 (c 2 5))) (c (a (f (r (r 39))) (c -9 (c 87 (c 3071 (c 0x017fff (c 2 (c (f (r (r (r 39)))) ()))))))) 1)) (q 8 (q . "assertion failed at cat.rue:149:9"))) 1) (q 2 (i (all 87 (= 0x017fff ())) (q . 2) (q 8 (q . "assertion failed at cat.rue:167:9"))) 1)) 1) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a -113 (c -113 (c 3071 (c 24575 ()))))) ())) (c (c (q . 61) (c (sha256 (concat (concat 91 (q . -53)) (a -113 (c -113 (c 87 (c 2 ())))))) ())) (f 19))) 1)) (c (+ (+ 12287 2) 24575) 1)) (c (- (f (r (r 1535))) (f (r 4))) 1)) (c (c (a 25 (c (c 25 (c 37 (c 45 (c 93 125)))) (c (a 47 95) (c 4 ())))) (c (a (i (l -65) (q 2 (i (= (f 767) (coinid (f -65) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r -65)) ())))))) (f (r (r -65))))) (q 1 . 1) (q)) 1) (q)) 1) (c (coinid (f 1535) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r 1535)) ())))))) (f (r (r 1535)))) (c (c 14 4) (c 10 767))))) 1)) (c (c (c 5 (c (sha256 (concat (q . 1) 5)) (c 11 ()))) (c (coinid (f 383) (f (r 383)) (f (r (r 383)))) (a 8 (c 8 23)))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 5)) (q 4 () (c () 15)) (q 2 (i (a (i (= (f (f 5)) (q . 51)) (q 2 (i (= (f (r (f 5))) ()) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (q 4 (f 2) (c (f (r 2)) (r (r 2)))) (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 (c (f (r (r (r (f 5))))) (c (f (r (r (r (r (f 5)))))) ())))))) 1)) (q 2 (q 4 (c (f 6) (f 4)) (c (+ (f (r 4)) (r 6)) (r (r 4)))) (c (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 15)))) (a (i (= (f (f 5)) (q . 51)) (q 2 (q 4 2 (f (r (r (f 11))))) (c (c (q . 51) (c (a 22 (c (c 10 (c 46 62)) (c (f 11) (c (f (r 11)) (c (sha256 (concat (q . 1) (f (r (r 11))))) (c (f (r (f 5))) ())))))) (c (f (r (r (f 5)))) (r (r (r (f 5))))))) 1)) (q 2 (i (a (i (= (f (f 5)) (q . 60)) (q 2 (i (all (= (strlen (f (r (f 5)))) (q . 33)) (= (substr (f (r (f 5))) () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8 (q . "raise called at cat.rue:234:13") (q . "Created coin announcements must not have the ring morph byte")) (q 4 (f 5) ())) 1)) 1)) 1))) 1)) 1)) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (q 2 (i (l 5) (q 4 (f 5) (a 2 (c 2 (c (r 5) 7)))) (q . 7)) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) solution: (0x00f43ce9fcc63d5019e209c103e6b0aaf56bbe7fc7fafae5af7f5ee6887a8719 0xd622c62a7292ffee5cf2537a90360ca0b7337b76d7014ec042930c0a87592213 (q (g1_negate () -113 (a (q 2 (i 47 (q 8) (q 2 (i (= 45 2) () (q 8)) 1)) 1) (c (q . 0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861) 1)) ()) (g1_negate 0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001 1 (0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001))) () () 0x615236766bed52d7abaa41d270407f3ec852981852334b213bd8515924459a5d (0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861 0x1ecb863db5d2ae6c71e9a8b0741acb3e034e8164b8ca0e564d5fad8b9dc875d5 1) (0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861 0x130deb20b44082a68293974f8cab9c51e21f9a9f3005000168eb77e49e0fc378 1) () ()) output: ((70 0x615236766bed52d7abaa41d270407f3ec852981852334b213bd8515924459a5d) (modpow 0xcb7f53b5de05b4afe58ab663b952aa785fd9ad911564709bd8eacbf4c58ba1e589) (% 0x389f1ea7b9fab7a9294104eb3a181d662f2dbe9c43fbe52802ba9b7eef357e77) (g1_negate 0xc9644528436f44cd9b33282684b4964f55d5551cb3a970ab9cf8f536e0d72ad1 1 (0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001))) runtime_cost: 297861 From 24aa254701ba322fb84ffa7348922dc1a7e1a7f8 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 16:29:02 -0400 Subject: [PATCH 06/21] More tests --- crates/rue-formatter/src/format.rs | 129 ++++- crates/rue-formatter/src/lib.rs | 6 +- crates/rue-formatter/src/tests.rs | 571 +++++++++++++++++++++++ crates/rue-formatter/src/token_stream.rs | 5 +- 4 files changed, 683 insertions(+), 28 deletions(-) diff --git a/crates/rue-formatter/src/format.rs b/crates/rue-formatter/src/format.rs index 3c9ccd11..0ece5d35 100644 --- a/crates/rue-formatter/src/format.rs +++ b/crates/rue-formatter/src/format.rs @@ -34,7 +34,9 @@ struct Layout { generic_tokens: HashSet, prefix_operators: HashSet, attached_openers: HashSet, + absolute_path_starts: HashSet, item_ends: HashSet, + module_item_ends: HashSet, block_item_ends: HashSet, groups: HashMap, binary_operators: HashMap>, @@ -72,35 +74,45 @@ pub(crate) fn format_document( consumed_comments: 0, }; - let mut docs = vec![formatter.gap_doc(&stream.gaps[0], Separator::None)]; let items = formatter.layout.document_items.clone(); + let (file_gap, first_item_gap) = if items.is_empty() { + (stream.gaps[0].clone(), Gap::default()) + } else { + split_first_item_gap(&stream.gaps[0]) + }; + let mut docs = vec![formatter.gap_doc(&file_gap, Separator::None)]; for (position, item) in items.iter().enumerate() { - if position > 0 || item.start != 0 { - let separator = if position == 0 { - Separator::None - } else { - let previous = &items[position - 1]; - match (previous.import_group, item.import_group) { - (Some(left), Some(right)) if left == right => Separator::Hard, - (None, None) - if previous.compact_group.is_some() - && previous.compact_group == item.compact_group - && (item.start == 0 - || stream.gaps[item.start].newlines <= 1) => - { - Separator::Hard - } - _ => Separator::Empty, + let separator = if position == 0 { + Separator::None + } else { + let previous = &items[position - 1]; + match (previous.import_group, item.import_group) { + (Some(left), Some(right)) if left == right => Separator::Hard, + (None, None) + if previous.compact_group.is_some() + && previous.compact_group == item.compact_group + && (item.start == 0 || stream.gaps[item.start].newlines <= 1) => + { + Separator::Hard } - }; - if item.start == 0 { - docs.push(separator_doc(separator)); - } else { - let mut gap = stream.gaps[item.start].clone(); - gap.newlines = 0; - docs.push(formatter.gap_doc(&gap, separator)); + _ => Separator::Empty, } + }; + let mut gap = if item.start == 0 { + first_item_gap.clone() + } else { + stream.gaps[item.start].clone() + }; + if position == 0 + && let Some(first) = gap.comments.first_mut() + { + first.newlines_before = 0; + first.trailing = false; + } + if gap.comments.is_empty() { + gap.newlines = 0; } + docs.push(formatter.gap_doc(&gap, separator)); docs.push(formatter.span(item.start, item.end)?); } if !stream.tokens.is_empty() { @@ -127,6 +139,31 @@ pub(crate) fn format_document( Ok(Doc::concat(docs)) } +fn split_first_item_gap(gap: &Gap) -> (Gap, Gap) { + if gap.comments.is_empty() || gap.newlines > 1 { + return (gap.clone(), Gap::default()); + } + + let mut attached_start = gap.comments.len() - 1; + while attached_start > 0 && gap.comments[attached_start].newlines_before <= 1 { + attached_start -= 1; + } + + let mut file_gap = Gap { + comments: gap.comments[..attached_start].to_vec(), + newlines: 0, + }; + let mut item_gap = Gap { + comments: gap.comments[attached_start..].to_vec(), + newlines: gap.newlines, + }; + if let Some(first) = item_gap.comments.first_mut() { + file_gap.newlines = first.newlines_before; + first.newlines_before = 0; + } + (file_gap, item_gap) +} + #[derive(Debug)] struct Formatter<'a> { stream: &'a TokenStream, @@ -439,6 +476,9 @@ impl Formatter<'_> { if self.layout.item_ends.contains(&left.start) { return Separator::Empty; } + if self.layout.module_item_ends.contains(&left.start) { + return Separator::Hard; + } if self.layout.block_item_ends.contains(&left.start) { return Separator::Hard; } @@ -449,6 +489,12 @@ impl Formatter<'_> { if left.kind == T![,] { return Separator::Soft; } + if right.kind == T![::] + && self.layout.absolute_path_starts.contains(&right.start) + && !no_space_after(left.kind) + { + return Separator::Space; + } if no_space_after(left.kind) || no_space_before(right.kind) { return Separator::None; } @@ -672,6 +718,29 @@ impl Layout { item_ends.insert(item_end); } } + let mut module_item_ends = HashSet::new(); + for module in root + .descendants() + .filter(|node| node.kind() == SyntaxKind::ModuleItem) + { + for item in module.children() { + if let Some(token) = significant_tokens(&item).last() { + module_item_ends.insert(usize::from(token.text_range().start())); + } + } + } + let absolute_path_starts = root + .descendants() + .filter(|node| { + matches!( + node.kind(), + SyntaxKind::PathExpr | SyntaxKind::PathType | SyntaxKind::ImportPath + ) + }) + .filter_map(|node| significant_tokens(&node).next()) + .filter(|token| token.kind() == T![::]) + .map(|token| usize::from(token.text_range().start())) + .collect(); let mut block_item_ends = HashSet::new(); for block in root .descendants() @@ -806,7 +875,7 @@ impl Layout { let is_import = matches!(&item, AstItem::ImportItem(_)); let import_group = is_import.then(|| { - if !previous_was_import || (start > 0 && stream.gaps[start].newlines > 1) { + if !previous_was_import || (start > 0 && gap_has_blank_line(&stream.gaps[start])) { next_import_group += 1; } next_import_group @@ -856,7 +925,9 @@ impl Layout { generic_tokens, prefix_operators, attached_openers, + absolute_path_starts, item_ends, + module_item_ends, block_item_ends, groups, binary_operators, @@ -866,6 +937,14 @@ impl Layout { } } +fn gap_has_blank_line(gap: &Gap) -> bool { + gap.newlines > 1 + || gap + .comments + .iter() + .any(|comment| comment.newlines_before > 1) +} + fn collect_binary_operator_starts(node: &SyntaxNode, operators: &mut Vec) { for element in node.children_with_tokens() { match element { diff --git a/crates/rue-formatter/src/lib.rs b/crates/rue-formatter/src/lib.rs index d3ef747c..602f0701 100644 --- a/crates/rue-formatter/src/lib.rs +++ b/crates/rue-formatter/src/lib.rs @@ -247,7 +247,7 @@ fn is_optional_trailing_comma(token: &rue_parser::SyntaxToken) -> bool { } fn comment_signature(stream: &TokenStream) -> Vec<(SyntaxKind, &str)> { - stream + let mut signature: Vec<_> = stream .gaps .iter() .flat_map(|gap| { @@ -255,7 +255,9 @@ fn comment_signature(stream: &TokenStream) -> Vec<(SyntaxKind, &str)> { .iter() .map(|comment| (comment.kind, comment.text.as_str())) }) - .collect() + .collect(); + signature.sort_unstable(); + signature } #[cfg(test)] diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index 5438d93c..9b737370 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -425,3 +425,574 @@ fn block_items_separate_implicit_return_expression() { "#]], ); } + +#[test] +fn empty_files_line_endings_and_final_newline() { + assert_eq!(format_source("", &FormatOptions::default()).unwrap(), "\n"); + assert_eq!( + format_source(" \t\r\n\r\n", &FormatOptions::default()).unwrap(), + "\n" + ); + check( + "const FIRST:Int=1;\r\nconst SECOND:Int=2;", + expect![[r#" + const FIRST: Int = 1; + const SECOND: Int = 2; + "#]], + ); +} + +#[test] +fn item_modifiers_modules_and_extern_functions() { + check( + r#"export mod api{export type Result=T|nil;export struct Wrapper{value:T}export inline const DEFAULT:Int=1;test fn smoke()->Int{DEFAULT}extern fn foreign(value:Int)->Int from "./foreign.hex";}"#, + expect![[r#" + export mod api { + export type Result = T | nil; + export struct Wrapper { + value: T, + } + export inline const DEFAULT: Int = 1; + test fn smoke() -> Int { + DEFAULT + } + extern fn foreign(value: Int) -> Int from "./foreign.hex"; + } + "#]], + ); +} + +#[test] +fn struct_field_forms() { + check( + "struct Example{opcode=42,value:T,optional:T=nil,...rest:Any=nil}", + expect![[r#" + struct Example { + opcode = 42, + value: T, + optional: T = nil, + ...rest: Any = nil, + } + "#]], + ); +} + +#[test] +fn statement_forms() { + check( + r#"fn statements(value:Int){inline let cached:Int=value;let declared:Int;let inferred=value;inline if value>0{debug value;}assert value!=0;raise "problem";raise;return value;return;value;}"#, + expect![[r#" + fn statements(value: Int) { + inline let cached: Int = value; + let declared: Int; + let inferred = value; + inline if value > 0 { + debug value; + } + assert value != 0; + raise "problem"; + raise; + return value; + return; + value; + } + "#]], + ); +} + +#[test] +fn literal_path_and_prefix_expressions() { + check( + r#"fn expressions(){42;0xff;0b1010;0o755;"text";true;false;nil;::root::value;super::value;!~+-42;}"#, + expect![[r#" + fn expressions() { + 42; + 0xff; + 0b1010; + 0o755; + "text"; + true; + false; + nil; + ::root::value; + super::value; + !~+-42; + } + "#]], + ); +} + +#[test] +fn collection_and_struct_expressions() { + check( + "fn expressions(){(first);(first,second,);[];[first,...rest,];Example{};Example{first,second:value,};{let value=1;value};const{1}}", + expect![[r#" + fn expressions() { + (first); + (first, second); + []; + [first, ...rest]; + Example {}; + Example { first, second: value }; + { + let value = 1; + value + }; + const { + 1 + } + } + "#]], + ); +} + +#[test] +fn calls_generics_spreads_and_postfix_expressions() { + check( + "fn expressions(value:Int,rest:List){::factory::make::(value,...rest,);value.field.next;value as Int;value is Int}", + expect![[r#" + fn expressions(value: Int, rest: List) { + ::factory::make::(value, ...rest); + value.field.next; + value as Int; + value is Int + } + "#]], + ); +} + +#[test] +fn lambda_and_conditional_expression_forms() { + check( + "fn expressions(value:Int){let identity=fn(item:T):T=>item;let pair=fn(...[first,...rest])=>(first,rest);let result=if value>0{1}else if value<0{-1}else{0};result}", + expect![[r#" + fn expressions(value: Int) { + let identity = fn(item: T): T => item; + let pair = fn(...[first, ...rest]) => (first, rest); + let result = if value > 0 { + 1 + } else if value < 0 { + -1 + } else { + 0 + }; + result + } + "#]], + ); +} + +#[test] +fn type_forms() { + check( + r#"type Paths=::root::Type;type Literals=42|"text"|true|nil;type Group=(Int);type Pair=(Int,String,);type List=[Int,...String,];type Callback=fn(value:T,...rest:[T])->T;"#, + expect![[r#" + type Paths = ::root::Type; + type Literals = 42 | "text" | true | nil; + type Group = (Int); + type Pair = (Int, String); + type List = [Int, ...String]; + type Callback = fn(value: T, ...rest: [T]) -> T; + "#]], + ); +} + +#[test] +fn binding_forms() { + check( + "fn bindings((left,right,):(Int,Int),...[first,...rest]:[Int,...Int],{name:alias,...remaining}:Any){let (a,b,)=(left,right);let [head,...tail]=[first,...rest];let {value:renamed,...others}=remaining;renamed}", + expect![[r#" + fn bindings( + (left, right): (Int, Int), + ...[first, ...rest]: [Int, ...Int], + { name: alias, ...remaining }: Any, + ) { + let (a, b) = (left, right); + let [head, ...tail] = [first, ...rest]; + let { value: renamed, ...others } = remaining; + renamed + } + "#]], + ); +} + +#[test] +fn every_binary_operator_has_canonical_spacing() { + check( + "fn operators(a:Int,b:Int){a||b;a&&b;a==b;a!=b;ab;a<=b;a>=b;a|b;a^b;a&b;a<>b;a>>>b;a+b;a-b;a*b;a/b;a%b}", + expect![[r#" + fn operators(a: Int, b: Int) { + a || b; + a && b; + a == b; + a != b; + a < b; + a > b; + a <= b; + a >= b; + a | b; + a ^ b; + a & b; + a << b; + a >> b; + a >>> b; + a + b; + a - b; + a * b; + a / b; + a % b + } + "#]], + ); +} + +#[test] +fn flat_trailing_commas_are_removed_everywhere() { + check( + "struct Item{value:T,}fn example(value:T,)->(T,T,){let pair=(value,value,);let list=[value,];let item=Item{value:value,};consume::(pair,list,item,)}", + expect![[r#" + struct Item { + value: T, + } + + fn example(value: T) -> (T, T) { + let pair = (value, value); + let list = [value]; + let item = Item { value: value }; + consume::(pair, list, item) + } + "#]], + ); +} + +#[test] +fn broken_lists_add_trailing_commas_everywhere() { + let options = FormatOptions { + max_width: 28, + ..FormatOptions::default() + }; + let output = format_source( + "struct LongStruct{first:FirstLongType,second:SecondLongType}fn call(first:FirstLongType,second:SecondLongType){LongStruct::{first:first,second:second};consume(first,second);[first,second]}", + &options, + ) + .unwrap(); + expect![[r#" + struct LongStruct { + first: FirstLongType, + second: SecondLongType, + } + + fn call( + first: FirstLongType, + second: SecondLongType, + ) { + LongStruct::< + FirstLongType, + > { + first: first, + second: second, + }; + consume(first, second); + [first, second] + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn comment_and_blank_line_boundaries() { + check( + "//! file\n\n/* before */const VALUE:Int=1;/* between */\n\nfn main(){let list=[/* open */1,/* item */2/* close */];let value=1/* before op */+/* after op */2;// trailing\n/* own line */\nvalue}// eof", + expect![[r#" + //! file + + /* before */ const VALUE: Int = 1; /* between */ + + fn main() { + let list = [/* open */ 1, /* item */ 2 /* close */ ]; + let value = 1 /* before op */ + /* after op */ 2; // trailing + /* own line */ + value + } // eof + "#]], + ); +} + +#[test] +fn nested_import_paths_are_sorted_without_crossing_groups() { + check( + "import root::{zeta::{two,one},alpha,super::thing};\nimport beta::*;\n\nexport zebra::{last,first};\nexport alpha;", + expect![[r#" + import beta::*; + import root::{alpha, super::thing, zeta::{one, two}}; + + export alpha; + export zebra::{first, last}; + "#]], + ); +} + +#[test] +fn custom_indentation_width_is_applied_consistently() { + let options = FormatOptions { + max_width: 24, + indent_width: 2, + }; + let output = format_source( + "fn main(){if first_condition&&second_condition{let values=[first_condition,second_condition];values}}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + if first_condition + && second_condition + { + let values = [ + first_condition, + second_condition, + ]; + values + } + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn empty_constructs_are_compact() { + check( + "mod empty{}struct Unit{}fn noop(){}fn values(){empty();[];Unit{};{}}", + expect![[r#" + mod empty {} + + struct Unit {} + + fn noop() {} + + fn values() { + empty(); + []; + Unit {}; + {} + } + "#]], + ); +} + +#[test] +fn operator_precedence_and_grouping_are_preserved() { + check( + "fn precedence(a:Int,b:Int,c:Int,d:Int){a+b*c<=second_long_value}", + &options, + ) + .unwrap(); + expect![[r#" + fn compare() { + first_long_value + == second_long_value; + first_long_value + != second_long_value; + first_long_value + <= second_long_value; + first_long_value + >= second_long_value + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn comments_inside_broken_delimiters() { + let options = FormatOptions { + max_width: 32, + ..FormatOptions::default() + }; + let output = format_source( + "fn comments(){consume(first_argument,// first\nsecond_argument,/* third */third_argument);[first_argument,// spread\n...remaining_arguments]}", + &options, + ) + .unwrap(); + expect![[r#" + fn comments() { + consume( + first_argument, // first + second_argument, /* third */ third_argument, + ); + [ + first_argument, // spread + ...remaining_arguments, + ] + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn long_types_and_bindings_break_consistently() { + let options = FormatOptions { + max_width: 36, + ..FormatOptions::default() + }; + let output = format_source( + "fn destructure({first:first_alias,second:second_alias,...remaining}:VeryLongContainerType)->fn(first:FirstLongType,second:SecondLongType)->VeryLongResultType{first_alias}", + &options, + ) + .unwrap(); + expect![[r#" + fn destructure( + { + first: first_alias, + second: second_alias, + ...remaining, + }: VeryLongContainerType, + ) -> fn( + first: FirstLongType, + second: SecondLongType, + ) -> VeryLongResultType { + first_alias + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn comments_between_if_else_branches_stay_attached() { + check( + "fn choose(value:Int)->Int{if value>0{// positive\n1}else if value<0{/* negative */-1}else{// zero\n0}}", + expect![[r#" + fn choose(value: Int) -> Int { + if value > 0 { + // positive + 1 + } else if value < 0 { + /* negative */ -1 + } else { + // zero + 0 + } + } + "#]], + ); +} + +#[test] +fn import_comments_move_with_their_imports() { + check( + "// zeta docs\nimport zeta;\n// alpha docs\nimport alpha;\n\n// beta export\nexport beta;", + expect![[r#" + // alpha docs + import alpha; + // zeta docs + import zeta; + + // beta export + export beta; + "#]], + ); +} + +#[test] +fn excessive_blank_lines_are_normalized_around_comments() { + check( + "const FIRST:Int=1;\n\n\n// second group\n\n\nconst SECOND:Int=2;\n\n\n\nfn main(){FIRST+SECOND}", + expect![[r#" + const FIRST: Int = 1; + + // second group + + const SECOND: Int = 2; + + fn main() { + FIRST + SECOND + } + "#]], + ); +} + +#[test] +fn malformed_input_matrix_is_rejected() { + for source in [ + "fn", + "fn main(", + "fn main(){", + "const VALUE:", + "type Value =", + "struct Value { field: }", + "import root::{value", + "fn main(){let = 1;}", + ] { + assert!( + matches!( + format_source(source, &FormatOptions::default()), + Err(FormatError::Parse { .. }) + ), + "expected parse error for {source:?}" + ); + } +} + +#[test] +fn very_narrow_width_still_produces_valid_idempotent_output() { + let options = FormatOptions { + max_width: 8, + indent_width: 2, + }; + let output = format_source( + "fn main(value:LongType)->LongType{consume(value,value)}", + &options, + ) + .unwrap(); + expect![[r#" + fn main( + value: LongType, + ) -> LongType { + consume( + value, + value, + ) + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn nested_generics_and_shift_operators_remain_unambiguous() { + check( + "type Nested=Outer>>;fn main(value:Int){build::>>(value);value>>2;value>>>3}", + expect![[r#" + type Nested = Outer>>; + + fn main(value: Int) { + build::>>(value); + value >> 2; + value >>> 3 + } + "#]], + ); +} diff --git a/crates/rue-formatter/src/token_stream.rs b/crates/rue-formatter/src/token_stream.rs index f1361c54..5529a70e 100644 --- a/crates/rue-formatter/src/token_stream.rs +++ b/crates/rue-formatter/src/token_stream.rs @@ -83,7 +83,10 @@ impl TokenStream { }); comment_count += 1; pending_newlines = 0; - if kind == SyntaxKind::LineComment || multiline { + if kind == SyntaxKind::LineComment { + pending_newlines = newline_count(raw.text()); + line_has_significant = false; + } else if multiline { line_has_significant = false; } } From b474d191a48aa4a60cbb0f105c1ab08ab465f49e Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 16:49:40 -0400 Subject: [PATCH 07/21] Thermo --- crates/rue-formatter/src/analysis.rs | 371 +++++++ crates/rue-formatter/src/document.rs | 19 +- crates/rue-formatter/src/emit.rs | 537 ++++++++++ crates/rue-formatter/src/equivalence.rs | 153 +++ crates/rue-formatter/src/format.rs | 1088 +------------------- crates/rue-formatter/src/lib.rs | 41 +- crates/rue-formatter/src/ordering.rs | 325 ++++++ crates/rue-formatter/src/renderer.rs | 130 +-- crates/rue-formatter/src/syntax.rs | 66 ++ crates/rue-formatter/src/tests.rs | 185 +--- crates/rue-formatter/src/tests/comments.rs | 125 +++ crates/rue-formatter/src/tests/imports.rs | 114 ++ crates/rue-formatter/src/token_stream.rs | 70 +- crates/rue-formatter/src/trivia.rs | 85 ++ 14 files changed, 1947 insertions(+), 1362 deletions(-) create mode 100644 crates/rue-formatter/src/analysis.rs create mode 100644 crates/rue-formatter/src/emit.rs create mode 100644 crates/rue-formatter/src/equivalence.rs create mode 100644 crates/rue-formatter/src/ordering.rs create mode 100644 crates/rue-formatter/src/syntax.rs create mode 100644 crates/rue-formatter/src/tests/comments.rs create mode 100644 crates/rue-formatter/src/tests/imports.rs create mode 100644 crates/rue-formatter/src/trivia.rs diff --git a/crates/rue-formatter/src/analysis.rs b/crates/rue-formatter/src/analysis.rs new file mode 100644 index 00000000..d240b5a5 --- /dev/null +++ b/crates/rue-formatter/src/analysis.rs @@ -0,0 +1,371 @@ +use rue_ast::{AstDocument, AstNode}; +use rue_parser::{SyntaxKind, SyntaxNode, T}; + +use crate::{ + FormatError, + ordering::{DocumentPlan, ImportGroupPlan, plan_document, plan_import_groups}, + token_stream::{TokenId, TokenStream}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum DelimiterStyle { + Block, + Braced, + Group, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ItemBoundary { + Document, + Module, + Block, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct TokenFacts { + pub(crate) pair: Option, + pub(crate) delimiter_style: Option, + pub(crate) item_boundary: Option, + pub(crate) group_end: Option, + pub(crate) binary_operators: Vec, + flags: TokenFlags, +} + +#[derive(Debug, Clone, Copy, Default)] +struct TokenFlags(u8); + +impl TokenFlags { + const TRAILING_COMMA: u8 = 1 << 0; + const GENERIC: u8 = 1 << 1; + const PREFIX_OPERATOR: u8 = 1 << 2; + const ATTACHED_OPENER: u8 = 1 << 3; + const ABSOLUTE_PATH_START: u8 = 1 << 4; + + fn insert(&mut self, flag: u8) { + self.0 |= flag; + } + + fn contains(self, flag: u8) -> bool { + self.0 & flag != 0 + } +} + +impl TokenFacts { + pub(crate) fn supports_trailing_comma(&self) -> bool { + self.flags.contains(TokenFlags::TRAILING_COMMA) + } + + pub(crate) fn is_generic(&self) -> bool { + self.flags.contains(TokenFlags::GENERIC) + } + + pub(crate) fn is_prefix_operator(&self) -> bool { + self.flags.contains(TokenFlags::PREFIX_OPERATOR) + } + + pub(crate) fn is_attached_opener(&self) -> bool { + self.flags.contains(TokenFlags::ATTACHED_OPENER) + } + + pub(crate) fn is_absolute_path_start(&self) -> bool { + self.flags.contains(TokenFlags::ABSOLUTE_PATH_START) + } +} + +#[derive(Debug)] +pub(crate) struct Layout { + facts: Vec, + pub(crate) document: DocumentPlan, + pub(crate) import_groups: std::collections::HashMap, +} + +impl Layout { + pub(crate) fn new(document: &AstDocument, stream: &TokenStream) -> Result { + let mut facts = vec![TokenFacts::default(); stream.tokens.len()]; + pair_delimiters(stream, &mut facts)?; + analyze_nodes(document.syntax(), stream, &mut facts)?; + analyze_boundaries(document, stream, &mut facts)?; + analyze_expression_groups(document.syntax(), stream, &mut facts)?; + + Ok(Self { + facts, + document: plan_document(document, stream)?, + import_groups: plan_import_groups(document.syntax(), stream)?, + }) + } + + pub(crate) fn facts(&self, token: TokenId) -> &TokenFacts { + &self.facts[token.index()] + } +} + +fn pair_delimiters(stream: &TokenStream, facts: &mut [TokenFacts]) -> Result<(), FormatError> { + let mut stack: Vec<(SyntaxKind, TokenId)> = Vec::new(); + for (index, token) in stream.tokens.iter().enumerate() { + let id = TokenId::new(index); + match token.kind { + T!['('] | T!['['] | T!['{'] => stack.push((token.kind, id)), + T![')'] | T![']'] | T!['}'] => { + let Some((open, open_id)) = stack.pop() else { + return Err(FormatError::Internal( + "closing delimiter has no opener".to_string(), + )); + }; + if !delimiters_match(open, token.kind) { + return Err(FormatError::Internal( + "mismatched delimiters in valid syntax".to_string(), + )); + } + facts[open_id.index()].pair = Some(id); + } + _ => {} + } + } + if !stack.is_empty() { + return Err(FormatError::Internal( + "opening delimiter has no closer".to_string(), + )); + } + Ok(()) +} + +fn analyze_nodes( + root: &SyntaxNode, + stream: &TokenStream, + facts: &mut [TokenFacts], +) -> Result<(), FormatError> { + for node in root.descendants() { + match node.kind() { + SyntaxKind::Block | SyntaxKind::ModuleItem | SyntaxKind::StructItem => { + if let Some(id) = direct_or_descendant_token(&node, T!['{'], stream)? { + facts[id.index()].delimiter_style = Some(DelimiterStyle::Block); + if node.kind() == SyntaxKind::StructItem { + facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); + } + } + } + SyntaxKind::StructInitializerExpr | SyntaxKind::StructBinding => { + if let Some(id) = direct_or_descendant_token(&node, T!['{'], stream)? { + facts[id.index()].delimiter_style = Some(DelimiterStyle::Braced); + facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); + } + } + SyntaxKind::PairExpr + | SyntaxKind::PairType + | SyntaxKind::PairBinding + | SyntaxKind::ListExpr + | SyntaxKind::ListType + | SyntaxKind::ListBinding + | SyntaxKind::ImportPathSegment => { + if let Some(token) = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .find(|token| matches!(token.kind(), T!['('] | T!['['] | T!['{'])) + { + let id = token_id(&token, stream)?; + facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); + } + } + SyntaxKind::GenericParameters | SyntaxKind::GenericArguments => { + let direct_tokens: Vec<_> = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .filter(|token| !token.kind().is_trivia()) + .collect(); + for token in + significant_tokens(&node).filter(|token| is_generic_punctuation(token.kind())) + { + let id = token_id(&token, stream)?; + facts[id.index()].flags.insert(TokenFlags::GENERIC); + } + if let (Some(open), Some(close)) = (direct_tokens.first(), direct_tokens.last()) + && open.kind() == T![<] + && close.kind() == T![>] + { + let open = token_id(open, stream)?; + let close = token_id(close, stream)?; + facts[open.index()].flags.insert(TokenFlags::TRAILING_COMMA); + facts[open.index()].pair = Some(close); + } + } + SyntaxKind::PrefixExpr => { + if let Some(token) = significant_tokens(&node) + .find(|token| SyntaxKind::PREFIX_OPS.contains(&token.kind())) + { + let id = token_id(&token, stream)?; + facts[id.index()].flags.insert(TokenFlags::PREFIX_OPERATOR); + } + } + SyntaxKind::FunctionItem + | SyntaxKind::FunctionCallExpr + | SyntaxKind::LambdaExpr + | SyntaxKind::LambdaType => { + if let Some(token) = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .find(|token| token.kind() == T!['(']) + { + let id = token_id(&token, stream)?; + facts[id.index()].flags.insert(TokenFlags::ATTACHED_OPENER); + facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); + } + } + SyntaxKind::PathExpr | SyntaxKind::PathType | SyntaxKind::ImportPath => { + if let Some(token) = significant_tokens(&node).next() + && token.kind() == T![::] + { + let id = token_id(&token, stream)?; + facts[id.index()] + .flags + .insert(TokenFlags::ABSOLUTE_PATH_START); + } + } + _ => {} + } + } + Ok(()) +} + +fn analyze_boundaries( + document: &AstDocument, + stream: &TokenStream, + facts: &mut [TokenFacts], +) -> Result<(), FormatError> { + for item in document.items() { + let Some(token) = significant_tokens(item.syntax()).last() else { + return Err(FormatError::Internal( + "document item has no significant token".to_string(), + )); + }; + let mut id = token_id(&token, stream)?; + if stream + .tokens + .get(id.next().index()) + .is_some_and(|token| token.kind == T![;]) + { + id = id.next(); + } + facts[id.index()].item_boundary = Some(ItemBoundary::Document); + } + + for (kind, boundary) in [ + (SyntaxKind::ModuleItem, ItemBoundary::Module), + (SyntaxKind::Block, ItemBoundary::Block), + ] { + for container in document + .syntax() + .descendants() + .filter(|node| node.kind() == kind) + { + for item in container.children() { + if let Some(token) = significant_tokens(&item).last() { + let id = token_id(&token, stream)?; + if facts[id.index()].item_boundary.is_none() { + facts[id.index()].item_boundary = Some(boundary); + } + } + } + } + } + Ok(()) +} + +fn analyze_expression_groups( + root: &SyntaxNode, + stream: &TokenStream, + facts: &mut [TokenFacts], +) -> Result<(), FormatError> { + for node in root + .descendants() + .filter(|node| matches!(node.kind(), SyntaxKind::BinaryExpr | SyntaxKind::UnionType)) + .filter(|node| { + node.parent() + .is_none_or(|parent| parent.kind() != node.kind()) + }) + { + let mut tokens = significant_tokens(&node); + let first = tokens.next().ok_or_else(|| { + FormatError::Internal("binary expression has no significant token".to_string()) + })?; + let last = tokens.last().unwrap_or_else(|| first.clone()); + let start = token_id(&first, stream)?; + let end = token_id(&last, stream)?; + facts[start.index()].group_end = Some( + facts[start.index()] + .group_end + .map_or(end, |current| current.max(end)), + ); + + let mut operator_offsets = Vec::new(); + if node.kind() == SyntaxKind::BinaryExpr { + collect_binary_operator_offsets(&node, &mut operator_offsets); + } else { + collect_union_operator_offsets(&node, &mut operator_offsets); + } + facts[start.index()].binary_operators = operator_offsets + .into_iter() + .map(|offset| stream.token_id_at_offset(offset)) + .collect::, _>>()?; + } + Ok(()) +} + +fn direct_or_descendant_token( + node: &SyntaxNode, + kind: SyntaxKind, + stream: &TokenStream, +) -> Result, FormatError> { + significant_tokens(node) + .find(|token| token.kind() == kind) + .map(|token| token_id(&token, stream)) + .transpose() +} + +fn token_id(token: &rue_parser::SyntaxToken, stream: &TokenStream) -> Result { + stream.token_id_at_offset(usize::from(token.text_range().start())) +} + +fn collect_binary_operator_offsets(node: &SyntaxNode, operators: &mut Vec) { + for element in node.children_with_tokens() { + match element { + rowan::NodeOrToken::Node(child) if child.kind() == SyntaxKind::BinaryExpr => { + collect_binary_operator_offsets(&child, operators); + } + rowan::NodeOrToken::Token(token) if SyntaxKind::BINARY_OPS.contains(&token.kind()) => { + operators.push(usize::from(token.text_range().start())); + } + _ => {} + } + } +} + +fn collect_union_operator_offsets(node: &SyntaxNode, operators: &mut Vec) { + for element in node.children_with_tokens() { + match element { + rowan::NodeOrToken::Node(child) if child.kind() == SyntaxKind::UnionType => { + collect_union_operator_offsets(&child, operators); + } + rowan::NodeOrToken::Token(token) if token.kind() == T![|] => { + operators.push(usize::from(token.text_range().start())); + } + _ => {} + } + } +} + +fn significant_tokens(node: &SyntaxNode) -> impl Iterator + '_ { + node.descendants_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .filter(|token| !token.kind().is_trivia()) +} + +fn is_generic_punctuation(kind: SyntaxKind) -> bool { + matches!(kind, T![<] | T![>] | T![,]) +} + +fn delimiters_match(open: SyntaxKind, close: SyntaxKind) -> bool { + matches!( + (open, close), + (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) + ) +} diff --git a/crates/rue-formatter/src/document.rs b/crates/rue-formatter/src/document.rs index 95546bd0..ea32a545 100644 --- a/crates/rue-formatter/src/document.rs +++ b/crates/rue-formatter/src/document.rs @@ -8,7 +8,10 @@ pub(crate) enum Doc { Line(LineKind), Indent(Box), Group(Box), - PreferredBreak { + /// A local fill boundary with continuation indentation applied only when + /// that boundary breaks. Ordinary groups cannot express that conditional + /// indentation when their surrounding binary chain is already broken. + Fill { doc: Box, indent_on_break: bool, }, @@ -50,6 +53,13 @@ impl Doc { Self::Line(LineKind::Soft) } + pub(crate) fn fill(doc: Self, indent_on_break: bool) -> Self { + Self::Fill { + doc: Box::new(doc), + indent_on_break, + } + } + pub(crate) fn hard_line() -> Self { Self::Line(LineKind::Hard) } @@ -66,13 +76,6 @@ impl Doc { Self::Group(Box::new(self)) } - pub(crate) fn preferred_break(doc: Self, indent_on_break: bool) -> Self { - Self::PreferredBreak { - doc: Box::new(doc), - indent_on_break, - } - } - pub(crate) fn if_break(broken: Self, flat: Self) -> Self { Self::IfBreak { broken: Box::new(broken), diff --git a/crates/rue-formatter/src/emit.rs b/crates/rue-formatter/src/emit.rs new file mode 100644 index 00000000..af893f81 --- /dev/null +++ b/crates/rue-formatter/src/emit.rs @@ -0,0 +1,537 @@ +use rue_parser::{SyntaxKind, T}; + +use crate::{ + FormatError, + analysis::{DelimiterStyle, ItemBoundary, Layout}, + document::Doc, + ordering::ImportGroupPlan, + token_stream::{Comment, CommentPlacement, Gap, TokenId, TokenSpan, TokenStream}, + trivia::Trivia, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Separator { + None, + Space, + Soft, + Hard, + Empty, +} + +pub(crate) struct Formatter<'a> { + stream: &'a TokenStream, + layout: Layout, + consumed_tokens: usize, + consumed_comments: usize, +} + +impl<'a> Formatter<'a> { + pub(crate) fn new(stream: &'a TokenStream, layout: Layout) -> Self { + Self { + stream, + layout, + consumed_tokens: 0, + consumed_comments: 0, + } + } + + pub(crate) fn format(mut self) -> Result { + let items = self.layout.document.items.clone(); + let header = self.layout.document.header.clone(); + let footer = self.layout.document.footer.clone(); + let mut docs = vec![self.trivia_doc(&header, Separator::None)]; + + for (position, item) in items.iter().enumerate() { + let separator = if position == 0 { + Separator::None + } else { + let previous = &items[position - 1]; + if previous.trailing.gap.comments.is_empty() { + match (previous.import_group, item.import_group) { + (Some(left), Some(right)) if left == right => Separator::Hard, + (None, None) + if previous.compact_group.is_some() + && previous.compact_group == item.compact_group + && (item.span.start.index() == 0 + || self.stream.gap_before(item.span.start).newlines <= 1) => + { + Separator::Hard + } + _ => Separator::Empty, + } + } else { + Separator::None + } + }; + let mut leading = item.leading.clone(); + if position == 0 + && let Some(first) = leading.gap.comments.first_mut() + { + first.newlines_before = 0; + first.placement = CommentPlacement::Leading; + } + docs.push(self.movable_leading_doc(&leading, separator)); + docs.push(self.span(item.span)?); + docs.push(self.trivia_doc(&item.trailing, Separator::None)); + } + docs.push(self.trivia_doc(&footer, Separator::None)); + + if self.consumed_tokens != self.stream.tokens.len() { + return Err(FormatError::Internal(format!( + "emitted {} of {} significant tokens", + self.consumed_tokens, + self.stream.tokens.len() + ))); + } + if self.consumed_comments != self.stream.comment_count { + return Err(FormatError::Internal(format!( + "emitted {} of {} comments", + self.consumed_comments, self.stream.comment_count + ))); + } + Ok(Doc::concat(docs)) + } + + fn span(&mut self, span: TokenSpan) -> Result { + self.span_inner(span, true) + } + + fn span_inner(&mut self, span: TokenSpan, allow_outer_group: bool) -> Result { + if allow_outer_group + && self + .layout + .facts(span.start) + .group_end + .is_some_and(|end| end.next() == span.end) + { + return self.grouped_span(span); + } + + let mut docs = Vec::new(); + let mut index = span.start; + while index.index() < span.end.index() { + let mut emitted_separator = false; + let facts = self.layout.facts(index); + let atom_end = if let Some(group_end) = facts.group_end + && group_end.index() < span.end.index() + && !(index == span.start && group_end.next() == span.end) + { + let next = group_end.next(); + let gap = self.stream.gap_before(next); + if !facts.binary_operators.is_empty() + && self.stream.tokens.get(next.index()).is_some_and(|_| { + self.layout + .facts(next) + .delimiter_style + .is_some_and(|style| style == DelimiterStyle::Block) + }) + && gap.comments.is_empty() + && gap.newlines <= 1 + { + docs.push( + Doc::concat([ + self.binary_span(TokenSpan::new(index, next))?, + Doc::if_break(Doc::hard_line(), Doc::space()), + ]) + .group(), + ); + emitted_separator = true; + } else { + docs.push(self.grouped_span(TokenSpan::new(index, next))?); + } + group_end + } else if let Some(close) = facts.pair { + if close.index() >= span.end.index() { + return Err(FormatError::Internal( + "delimiter pair crosses formatting span".to_string(), + )); + } + docs.push(self.delimited(index, close)?); + close + } else { + docs.push(self.token(index)); + index + }; + + index = atom_end.next(); + if index.index() < span.end.index() && !emitted_separator { + let separator = self.separator(atom_end, index); + docs.push(self.gap_doc(self.stream.gap_before(index), separator)); + } + } + Ok(Doc::concat(docs)) + } + + fn grouped_span(&mut self, span: TokenSpan) -> Result { + if !self.layout.facts(span.start).binary_operators.is_empty() { + return Ok(self.binary_span(span)?.group()); + } + Ok(self.span_inner(span, false)?.indent().group()) + } + + fn binary_span(&mut self, span: TokenSpan) -> Result { + let operators = self.layout.facts(span.start).binary_operators.clone(); + let Some(&first_operator) = operators.first() else { + return self.span_inner(span, false); + }; + + let mut docs = vec![self.span_inner(TokenSpan::new(span.start, first_operator), false)?]; + for (position, operator) in operators.iter().enumerate() { + let operand_start = operator.next(); + let segment_end = operators.get(position + 1).copied().unwrap_or(span.end); + let operator_doc = self.token(*operator); + let after_operator = + self.gap_doc(self.stream.gap_before(operand_start), Separator::Space); + let operand = self.span_inner(TokenSpan::new(operand_start, segment_end), false)?; + let segment = Doc::concat([operator_doc, after_operator, operand]); + if is_comparison_operator(self.stream.token(*operator).kind) + && self.stream.gap_before(*operator).comments.is_empty() + { + let fill = Doc::fill(segment, position == 0); + docs.push(if position == 0 { fill } else { fill.indent() }); + } else { + let before_operator = + self.gap_doc(self.stream.gap_before(*operator), Separator::Soft); + docs.push(Doc::concat([before_operator, segment]).indent()); + } + } + Ok(Doc::concat(docs)) + } + + fn delimited(&mut self, open: TokenId, close: TokenId) -> Result { + let facts = self.layout.facts(open); + let style = facts.delimiter_style.unwrap_or(DelimiterStyle::Group); + let supports_trailing_comma = facts.supports_trailing_comma(); + let open_doc = self.token(open); + let close_doc = self.token(close); + let inner_start = open.next(); + + if inner_start == close { + let gap = self.gap_doc_with_comments( + self.stream.gap_before(inner_start), + if self.stream.gap_before(inner_start).comments.is_empty() { + Separator::None + } else if style == DelimiterStyle::Block { + Separator::Hard + } else { + Separator::Soft + }, + true, + false, + ); + return Ok(Doc::concat([open_doc, gap, close_doc]).group()); + } + + if let Some(plan) = self.layout.import_groups.get(&open).cloned() { + return self.import_group(open_doc, close_doc, &plan); + } + + let source_trailing_comma = supports_trailing_comma + && self.stream.token(TokenId::new(close.index() - 1)).kind == T![,]; + let inner_end = TokenId::new(close.index() - usize::from(source_trailing_comma)); + let inner = self.span(TokenSpan::new(inner_start, inner_end))?; + let comma = if source_trailing_comma { + let leading = self.gap_doc(self.stream.gap_before(inner_end), Separator::None); + self.consumed_tokens += 1; + Doc::concat([leading, Doc::if_break(Doc::text(","), Doc::Nil)]) + } else if supports_trailing_comma { + Doc::if_break(Doc::text(","), Doc::Nil) + } else { + Doc::Nil + }; + let inner = Doc::concat([inner, comma]); + let leading_gap = self.stream.gap_before(inner_start); + let trailing_gap = self.stream.gap_before(close); + let leading_comment_starts_line = leading_gap + .comments + .first() + .is_some_and(|comment| comment.newlines_before > 0 || comment.multiline); + let trailing_comment_ends_line = trailing_gap.comments.last().is_some_and(|comment| { + comment.kind == SyntaxKind::LineComment + || comment.multiline + || trailing_gap.newlines > 0 + }); + let leading = self.gap_doc_with_comments( + leading_gap, + match style { + DelimiterStyle::Block => Separator::Hard, + DelimiterStyle::Braced => Separator::Soft, + DelimiterStyle::Group => Separator::None, + }, + true, + false, + ); + let trailing = self.gap_doc( + trailing_gap, + match style { + DelimiterStyle::Block => Separator::Hard, + DelimiterStyle::Braced => Separator::Soft, + DelimiterStyle::Group => Separator::None, + }, + ); + + Ok(match style { + DelimiterStyle::Block | DelimiterStyle::Braced => Doc::concat([ + open_doc, + Doc::concat([leading, inner]).indent(), + trailing, + close_doc, + ]) + .group_if(style == DelimiterStyle::Braced), + DelimiterStyle::Group => Doc::concat([ + open_doc, + Doc::concat([ + if leading_comment_starts_line { + Doc::Nil + } else { + Doc::if_break(Doc::hard_line(), Doc::Nil) + }, + leading, + inner, + ]) + .indent(), + trailing, + if trailing_comment_ends_line { + Doc::Nil + } else { + Doc::if_break(Doc::hard_line(), Doc::Nil) + }, + close_doc, + ]) + .group(), + }) + } + + fn import_group( + &mut self, + open: Doc, + close: Doc, + plan: &ImportGroupPlan, + ) -> Result { + let mut docs = vec![self.trivia_doc(&plan.opening, Separator::None)]; + for (position, item) in plan.items.iter().enumerate() { + let mut leading = item.leading.clone(); + if position == 0 + && let Some(first) = leading.gap.comments.first_mut() + { + first.newlines_before = 0; + } + docs.push(self.movable_leading_doc( + &leading, + if position == 0 { + Separator::None + } else { + Separator::Soft + }, + )); + docs.push(self.span(item.span)?); + docs.push(self.trivia_doc(&item.before_comma, Separator::None)); + if item.comma.is_some() { + self.consumed_tokens += 1; + } + if position + 1 < plan.items.len() { + docs.push(Doc::text(",")); + } else { + docs.push(Doc::if_break(Doc::text(","), Doc::Nil)); + } + let suppress_final_line = position + 1 == plan.items.len() + && plan.closing.gap.comments.is_empty() + && trivia_ends_line(&item.trailing); + docs.push(self.trivia_doc_with_final_line( + &item.trailing, + Separator::None, + !suppress_final_line, + )); + } + docs.push(self.trivia_doc_with_final_line( + &plan.closing, + Separator::None, + !trivia_ends_line(&plan.closing), + )); + Ok(Doc::concat([ + open, + Doc::concat([Doc::if_break(Doc::hard_line(), Doc::Nil), Doc::concat(docs)]).indent(), + Doc::if_break(Doc::hard_line(), Doc::Nil), + close, + ]) + .group()) + } + + fn token(&mut self, id: TokenId) -> Doc { + self.consumed_tokens += 1; + Doc::text(self.stream.token(id).text.clone()) + } + + fn separator(&self, previous: TokenId, next: TokenId) -> Separator { + let left = self.stream.token(previous); + let right = self.stream.token(next); + if let Some(boundary) = self.layout.facts(previous).item_boundary { + return match boundary { + ItemBoundary::Document => Separator::Empty, + ItemBoundary::Module | ItemBoundary::Block => Separator::Hard, + }; + } + if left.kind == T![;] { + return Separator::Hard; + } + if left.kind == T![,] { + return Separator::Soft; + } + if right.kind == T![::] + && self.layout.facts(next).is_absolute_path_start() + && !no_space_after(left.kind) + { + return Separator::Space; + } + if no_space_after(left.kind) || no_space_before(right.kind) { + return Separator::None; + } + if SyntaxKind::BINARY_OPS.contains(&left.kind) + && !self.layout.facts(previous).is_generic() + && !self.layout.facts(previous).is_prefix_operator() + { + return Separator::Soft; + } + if right.kind == T!['('] && self.layout.facts(next).is_attached_opener() { + return Separator::None; + } + if left.kind == T!['}'] && right.kind == T![else] { + return Separator::Space; + } + if ((left.kind == T![<] + || right.kind == T![<] + || right.kind == T![>] + || (left.kind == T![>] && right.kind == T!['('])) + && (self.layout.facts(previous).is_generic() || self.layout.facts(next).is_generic())) + || (SyntaxKind::PREFIX_OPS.contains(&left.kind) + && self.layout.facts(previous).is_prefix_operator()) + { + Separator::None + } else { + Separator::Space + } + } + + fn trivia_doc(&mut self, trivia: &Trivia, requested: Separator) -> Doc { + self.gap_doc(&trivia.gap, requested) + } + + fn movable_leading_doc(&mut self, trivia: &Trivia, requested: Separator) -> Doc { + if trivia.gap.comments.is_empty() { + separator_doc(requested) + } else { + self.trivia_doc(trivia, requested) + } + } + + fn gap_doc(&mut self, gap: &Gap, requested: Separator) -> Doc { + self.gap_doc_with_comments(gap, requested, false, false) + } + + fn trivia_doc_with_final_line( + &mut self, + trivia: &Trivia, + requested: Separator, + include_final_line: bool, + ) -> Doc { + self.gap_doc_with_comments(&trivia.gap, requested, false, !include_final_line) + } + + fn gap_doc_with_comments( + &mut self, + gap: &Gap, + requested: Separator, + ignore_trailing: bool, + suppress_final_line: bool, + ) -> Doc { + if gap.comments.is_empty() { + if gap.newlines > 1 && requested != Separator::None { + return Doc::empty_line(); + } + return separator_doc(requested); + } + + let mut docs = Vec::new(); + for (index, comment) in gap.comments.iter().enumerate() { + self.consumed_comments += 1; + let before = if index == 0 { + comment_separator(comment, requested, ignore_trailing) + } else { + comment_separator(comment, Separator::Hard, false) + }; + docs.push(before); + docs.push(Doc::text(comment.text.clone())); + let followed_inline = match gap.comments.get(index + 1) { + Some(next) => next.newlines_before == 0, + None => gap.newlines == 0, + }; + if comment.kind == SyntaxKind::BlockComment && !comment.multiline && followed_inline { + docs.push(Doc::space()); + } + } + let last = gap.comments.last().expect("comments are not empty"); + if suppress_final_line { + // The enclosing delimiter emits the required line at its own + // indentation, avoiding spaces before the closing token. + } else if gap.newlines > 1 { + docs.push(Doc::empty_line()); + } else if last.kind == SyntaxKind::LineComment || last.multiline || gap.newlines > 0 { + docs.push(Doc::hard_line()); + } + Doc::concat(docs) + } +} + +trait GroupIf { + fn group_if(self, condition: bool) -> Self; +} + +impl GroupIf for Doc { + fn group_if(self, condition: bool) -> Self { + if condition { self.group() } else { self } + } +} + +fn separator_doc(separator: Separator) -> Doc { + match separator { + Separator::None => Doc::Nil, + Separator::Space => Doc::space(), + Separator::Soft => Doc::soft_line(), + Separator::Hard => Doc::hard_line(), + Separator::Empty => Doc::empty_line(), + } +} + +fn comment_separator(comment: &Comment, requested: Separator, ignore_trailing: bool) -> Doc { + if comment.placement == CommentPlacement::Trailing && !ignore_trailing { + return Doc::space(); + } + if comment.newlines_before > 1 { + return Doc::empty_line(); + } + if comment.newlines_before > 0 || comment.multiline { + return Doc::hard_line(); + } + separator_doc(requested) +} + +fn trivia_ends_line(trivia: &Trivia) -> bool { + trivia.gap.comments.last().is_some_and(|comment| { + comment.kind == SyntaxKind::LineComment || comment.multiline || trivia.gap.newlines > 0 + }) +} + +fn no_space_after(kind: SyntaxKind) -> bool { + matches!(kind, T!['('] | T!['['] | T![::] | T![.] | T![...] | T![,]) +} + +fn no_space_before(kind: SyntaxKind) -> bool { + matches!( + kind, + T![')'] | T![']'] | T![,] | T![;] | T![::] | T![.] | T![:] + ) +} + +fn is_comparison_operator(kind: SyntaxKind) -> bool { + matches!(kind, T![==] | T![!=] | T![<] | T![>] | T![<=] | T![>=]) +} diff --git a/crates/rue-formatter/src/equivalence.rs b/crates/rue-formatter/src/equivalence.rs new file mode 100644 index 00000000..ea277cce --- /dev/null +++ b/crates/rue-formatter/src/equivalence.rs @@ -0,0 +1,153 @@ +use std::collections::HashSet; + +use rue_ast::{AstDocument, AstNode}; + +use crate::{ + FormatError, + ordering::{plan_document, plan_import_groups}, + token_stream::{TokenSpan, TokenStream}, + trivia::Trivia, +}; + +/// Produces a canonical, ownership-sensitive comment signature. Reorderable +/// imports and paths are keyed by their significant-token content, while the +/// role and order of each comment within that unit remain significant. +pub(crate) fn comment_signature( + document: &AstDocument, + stream: &TokenStream, +) -> Result, FormatError> { + let document_plan = plan_document(document, stream)?; + let import_groups = plan_import_groups(document.syntax(), stream)?; + let mut signature = Vec::new(); + let mut covered_gaps = HashSet::new(); + + append_trivia(&mut signature, "file", "header", &document_plan.header); + append_trivia(&mut signature, "file", "footer", &document_plan.footer); + covered_gaps.insert(stream.tokens.len()); + + let document_units: Vec<_> = document_plan + .items + .iter() + .map(|item| { + covered_gaps.insert(item.span.start.index()); + let anchor = item.identity_key.clone(); + append_trivia(&mut signature, &anchor, "leading", &item.leading); + append_trivia(&mut signature, &anchor, "trailing", &item.trailing); + (item.span, anchor) + }) + .collect(); + + let mut path_units = Vec::new(); + for (open, group) in &import_groups { + let group_key = group + .items + .iter() + .map(|item| item.identity_key.clone()) + .collect::>() + .join("|"); + let anchor = format!("group:{group_key}"); + append_trivia(&mut signature, &anchor, "dangling-open", &group.opening); + append_trivia(&mut signature, &anchor, "dangling-close", &group.closing); + covered_gaps.insert(open.index() + 1); + let close = find_close(*open, stream)?; + covered_gaps.insert(close.index()); + + for item in &group.items { + let anchor = format!("path:{}", item.identity_key); + covered_gaps.insert(item.span.start.index()); + if let Some(comma) = item.comma { + covered_gaps.insert(comma.index()); + } + append_trivia(&mut signature, &anchor, "leading", &item.leading); + append_trivia(&mut signature, &anchor, "before-comma", &item.before_comma); + append_trivia(&mut signature, &anchor, "trailing", &item.trailing); + path_units.push((item.span, anchor)); + } + } + + for (gap_index, gap) in stream.gaps.iter().enumerate() { + if covered_gaps.contains(&gap_index) { + continue; + } + let (anchor, relative_gap) = path_units + .iter() + .find(|(span, _)| contains_gap(*span, gap_index)) + .or_else(|| { + document_units + .iter() + .find(|(span, _)| contains_gap(*span, gap_index)) + }) + .map_or_else( + || ("file".to_string(), gap_index), + |(span, anchor)| { + ( + anchor.clone(), + normalized_relative_gap(stream, *span, gap_index), + ) + }, + ); + for (order, comment) in gap.comments.iter().enumerate() { + signature.push(format!( + "{anchor}\0inner:{relative_gap}:{order}\0{:?}\0{}", + comment.kind, comment.text + )); + } + } + + signature.sort(); + Ok(signature) +} + +fn append_trivia(signature: &mut Vec, anchor: &str, role: &str, trivia: &Trivia) { + for (order, comment) in trivia.gap.comments.iter().enumerate() { + signature.push(format!( + "{anchor}\0{role}:{order}\0{:?}\0{}", + comment.kind, comment.text + )); + } +} + +fn normalized_relative_gap(stream: &TokenStream, span: TokenSpan, gap: usize) -> usize { + (span.start.index()..gap) + .filter(|index| !is_optional_trailing_comma(stream, *index)) + .count() +} + +fn is_optional_trailing_comma(stream: &TokenStream, index: usize) -> bool { + stream + .tokens + .get(index) + .is_some_and(|token| token.kind == rue_parser::T![,]) + && stream.tokens.get(index + 1).is_some_and(|token| { + matches!( + token.kind, + rue_parser::T![')'] | rue_parser::T![']'] | rue_parser::T!['}'] | rue_parser::T![>] + ) + }) +} + +fn contains_gap(span: TokenSpan, gap: usize) -> bool { + span.start.index() < gap && gap < span.end.index() +} + +fn find_close( + open: crate::token_stream::TokenId, + stream: &TokenStream, +) -> Result { + let mut depth = 0; + for index in open.index()..stream.tokens.len() { + match stream.tokens[index].kind { + rue_parser::T!['{'] => depth += 1, + rue_parser::T!['}'] => { + depth -= 1; + if depth == 0 { + return Ok(crate::token_stream::TokenId::new(index)); + } + } + _ => {} + } + } + Err(FormatError::Internal( + "import path group has no closing delimiter".to_string(), + )) +} diff --git a/crates/rue-formatter/src/format.rs b/crates/rue-formatter/src/format.rs index 0ece5d35..c34e8283 100644 --- a/crates/rue-formatter/src/format.rs +++ b/crates/rue-formatter/src/format.rs @@ -1,1095 +1,15 @@ -use std::collections::{HashMap, HashSet}; - -use rue_ast::{AstDocument, AstItem, AstNode}; -use rue_parser::{SyntaxKind, SyntaxNode, T}; +use rue_ast::{AstDocument, AstNode}; use crate::{ - FormatError, - document::Doc, - token_stream::{Comment, Gap, TokenStream}, + FormatError, analysis::Layout, document::Doc, emit::Formatter, syntax::validate_node_kinds, + token_stream::TokenStream, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum DelimiterStyle { - Block, - Braced, - Group, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Separator { - None, - Space, - Soft, - Hard, - Empty, -} - -#[derive(Debug)] -struct Layout { - pairs: HashMap, - block_openers: HashSet, - braced_openers: HashSet, - trailing_comma_openers: HashSet, - generic_tokens: HashSet, - prefix_operators: HashSet, - attached_openers: HashSet, - absolute_path_starts: HashSet, - item_ends: HashSet, - module_item_ends: HashSet, - block_item_ends: HashSet, - groups: HashMap, - binary_operators: HashMap>, - document_items: Vec, - import_groups: HashMap>, -} - -#[derive(Debug, Clone)] -struct DocumentItem { - start: usize, - end: usize, - import_group: Option, - compact_group: Option, - sort_key: String, - original_index: usize, -} - -#[derive(Debug, Clone)] -struct ImportGroupItem { - start: usize, - end: usize, - sort_key: String, -} - pub(crate) fn format_document( document: &AstDocument, stream: &TokenStream, ) -> Result { validate_node_kinds(document.syntax())?; let layout = Layout::new(document, stream)?; - let mut formatter = Formatter { - stream, - layout, - consumed_tokens: 0, - consumed_comments: 0, - }; - - let items = formatter.layout.document_items.clone(); - let (file_gap, first_item_gap) = if items.is_empty() { - (stream.gaps[0].clone(), Gap::default()) - } else { - split_first_item_gap(&stream.gaps[0]) - }; - let mut docs = vec![formatter.gap_doc(&file_gap, Separator::None)]; - for (position, item) in items.iter().enumerate() { - let separator = if position == 0 { - Separator::None - } else { - let previous = &items[position - 1]; - match (previous.import_group, item.import_group) { - (Some(left), Some(right)) if left == right => Separator::Hard, - (None, None) - if previous.compact_group.is_some() - && previous.compact_group == item.compact_group - && (item.start == 0 || stream.gaps[item.start].newlines <= 1) => - { - Separator::Hard - } - _ => Separator::Empty, - } - }; - let mut gap = if item.start == 0 { - first_item_gap.clone() - } else { - stream.gaps[item.start].clone() - }; - if position == 0 - && let Some(first) = gap.comments.first_mut() - { - first.newlines_before = 0; - first.trailing = false; - } - if gap.comments.is_empty() { - gap.newlines = 0; - } - docs.push(formatter.gap_doc(&gap, separator)); - docs.push(formatter.span(item.start, item.end)?); - } - if !stream.tokens.is_empty() { - docs.push(formatter.gap_doc( - stream.gaps.last().expect("a trailing gap always exists"), - Separator::None, - )); - } - - if formatter.consumed_tokens != stream.tokens.len() { - return Err(FormatError::Internal(format!( - "emitted {} of {} significant tokens", - formatter.consumed_tokens, - stream.tokens.len() - ))); - } - if formatter.consumed_comments != stream.comment_count { - return Err(FormatError::Internal(format!( - "emitted {} of {} comments", - formatter.consumed_comments, stream.comment_count - ))); - } - - Ok(Doc::concat(docs)) -} - -fn split_first_item_gap(gap: &Gap) -> (Gap, Gap) { - if gap.comments.is_empty() || gap.newlines > 1 { - return (gap.clone(), Gap::default()); - } - - let mut attached_start = gap.comments.len() - 1; - while attached_start > 0 && gap.comments[attached_start].newlines_before <= 1 { - attached_start -= 1; - } - - let mut file_gap = Gap { - comments: gap.comments[..attached_start].to_vec(), - newlines: 0, - }; - let mut item_gap = Gap { - comments: gap.comments[attached_start..].to_vec(), - newlines: gap.newlines, - }; - if let Some(first) = item_gap.comments.first_mut() { - file_gap.newlines = first.newlines_before; - first.newlines_before = 0; - } - (file_gap, item_gap) -} - -#[derive(Debug)] -struct Formatter<'a> { - stream: &'a TokenStream, - layout: Layout, - consumed_tokens: usize, - consumed_comments: usize, -} - -impl Formatter<'_> { - fn span(&mut self, start: usize, end: usize) -> Result { - self.span_inner(start, end, true) - } - - fn span_inner( - &mut self, - start: usize, - end: usize, - allow_outer_group: bool, - ) -> Result { - if allow_outer_group - && self - .layout - .groups - .get(&start) - .is_some_and(|group_end| *group_end + 1 == end) - { - return self.grouped_span(start, end); - } - - let mut docs = Vec::new(); - let mut index = start; - - while index < end { - let mut emitted_separator = false; - let atom_end = if let Some(&group_end) = self.layout.groups.get(&index) - && group_end < end - && !(index == start && group_end + 1 == end) - { - let next = group_end + 1; - let gap = &self.stream.gaps[next]; - if self.layout.binary_operators.contains_key(&index) - && self - .stream - .tokens - .get(next) - .is_some_and(|token| self.layout.block_openers.contains(&token.start)) - && gap.comments.is_empty() - && gap.newlines <= 1 - { - docs.push( - Doc::concat([ - self.binary_span(index, next)?, - Doc::if_break(Doc::hard_line(), Doc::space()), - ]) - .group(), - ); - emitted_separator = true; - } else { - docs.push(self.grouped_span(index, next)?); - } - group_end - } else if let Some(&close) = self.layout.pairs.get(&index) { - if close >= end { - return Err(FormatError::Internal( - "delimiter pair crosses formatting span".to_string(), - )); - } - docs.push(self.delimited(index, close)?); - close - } else { - docs.push(self.token(index)); - index - }; - - index = atom_end + 1; - if index < end && !emitted_separator { - let separator = self.separator(atom_end, index); - docs.push(self.gap_doc(&self.stream.gaps[index], separator)); - } - } - - Ok(Doc::concat(docs)) - } - - fn grouped_span(&mut self, start: usize, end: usize) -> Result { - if self.layout.binary_operators.contains_key(&start) { - return Ok(self.binary_span(start, end)?.group()); - } - Ok(self.span_inner(start, end, false)?.indent().group()) - } - - fn binary_span(&mut self, start: usize, end: usize) -> Result { - let operators = self - .layout - .binary_operators - .get(&start) - .cloned() - .unwrap_or_default(); - - let Some(&first_operator) = operators.first() else { - return self.span_inner(start, end, false); - }; - - let mut docs = vec![self.span_inner(start, first_operator, false)?]; - for (position, operator) in operators.iter().enumerate() { - let operand_start = operator + 1; - let segment_end = operators.get(position + 1).copied().unwrap_or(end); - let operator_doc = self.token(*operator); - let after_operator = self.gap_doc(&self.stream.gaps[operand_start], Separator::Space); - let operand = self.span_inner(operand_start, segment_end, false)?; - let segment = Doc::concat([operator_doc, after_operator, operand]); - if is_comparison_operator(self.stream.tokens[*operator].kind) - && self.stream.gaps[*operator].comments.is_empty() - { - let preferred = Doc::preferred_break(segment, position == 0); - docs.push(if position == 0 { - preferred - } else { - preferred.indent() - }); - } else { - let before_operator = self.gap_doc(&self.stream.gaps[*operator], Separator::Soft); - docs.push(Doc::concat([before_operator, segment]).indent()); - } - } - Ok(Doc::concat(docs)) - } - - fn delimited(&mut self, open: usize, close: usize) -> Result { - let style = if self - .layout - .block_openers - .contains(&self.stream.tokens[open].start) - { - DelimiterStyle::Block - } else if self - .layout - .braced_openers - .contains(&self.stream.tokens[open].start) - { - DelimiterStyle::Braced - } else { - DelimiterStyle::Group - }; - let open_doc = self.token(open); - let close_doc = self.token(close); - let inner_start = open + 1; - let supports_trailing_comma = self - .layout - .trailing_comma_openers - .contains(&self.stream.tokens[open].start); - - if inner_start == close { - let gap = self.gap_doc_with_comments( - &self.stream.gaps[inner_start], - if self.stream.gaps[inner_start].comments.is_empty() { - Separator::None - } else if style == DelimiterStyle::Block { - Separator::Hard - } else { - Separator::Soft - }, - true, - ); - return Ok(Doc::concat([open_doc, gap, close_doc]).group()); - } - - if let Some(items) = self.layout.import_groups.get(&open).cloned() { - return self.import_group(open_doc, close_doc, close, &items); - } - - let source_trailing_comma = - supports_trailing_comma && self.stream.tokens[close - 1].kind == T![,]; - let inner_end = close - usize::from(source_trailing_comma); - let inner = self.span(inner_start, inner_end)?; - let comma = if source_trailing_comma { - let leading = self.gap_doc(&self.stream.gaps[inner_end], Separator::None); - self.consumed_tokens += 1; - Doc::concat([leading, Doc::if_break(Doc::text(","), Doc::Nil)]) - } else if supports_trailing_comma { - Doc::if_break(Doc::text(","), Doc::Nil) - } else { - Doc::Nil - }; - let inner = Doc::concat([inner, comma]); - let leading_gap = &self.stream.gaps[inner_start]; - let trailing_gap = &self.stream.gaps[close]; - let leading_comment_starts_line = leading_gap - .comments - .first() - .is_some_and(|comment| comment.newlines_before > 0 || comment.multiline); - let trailing_comment_ends_line = trailing_gap.comments.last().is_some_and(|comment| { - comment.kind == SyntaxKind::LineComment - || comment.multiline - || trailing_gap.newlines > 0 - }); - let leading = self.gap_doc_with_comments( - leading_gap, - match style { - DelimiterStyle::Block => Separator::Hard, - DelimiterStyle::Braced => Separator::Soft, - DelimiterStyle::Group => Separator::None, - }, - true, - ); - let trailing = self.gap_doc( - trailing_gap, - match style { - DelimiterStyle::Block => Separator::Hard, - DelimiterStyle::Braced => Separator::Soft, - DelimiterStyle::Group => Separator::None, - }, - ); - - Ok(match style { - DelimiterStyle::Block => Doc::concat([ - open_doc, - Doc::concat([leading, inner]).indent(), - trailing, - close_doc, - ]), - DelimiterStyle::Braced => Doc::concat([ - open_doc, - Doc::concat([leading, inner]).indent(), - trailing, - close_doc, - ]) - .group(), - DelimiterStyle::Group => Doc::concat([ - open_doc, - Doc::concat([ - if leading_comment_starts_line { - Doc::Nil - } else { - Doc::if_break(Doc::hard_line(), Doc::Nil) - }, - leading, - inner, - ]) - .indent(), - trailing, - if trailing_comment_ends_line { - Doc::Nil - } else { - Doc::if_break(Doc::hard_line(), Doc::Nil) - }, - close_doc, - ]) - .group(), - }) - } - - fn import_group( - &mut self, - open: Doc, - close: Doc, - close_index: usize, - items: &[ImportGroupItem], - ) -> Result { - let mut docs = Vec::new(); - for (position, item) in items.iter().enumerate() { - let requested = if position == 0 { - Separator::None - } else { - Separator::Soft - }; - let mut gap = self.stream.gaps[item.start].clone(); - gap.newlines = 0; - docs.push(self.gap_doc(&gap, requested)); - docs.push(self.span(item.start, item.end)?); - - if self - .stream - .tokens - .get(item.end) - .is_some_and(|token| token.kind == T![,]) - { - let comma_gap = self.gap_doc(&self.stream.gaps[item.end], Separator::None); - docs.push(comma_gap); - self.consumed_tokens += 1; - } - - if position + 1 < items.len() { - docs.push(Doc::text(",")); - } else { - docs.push(Doc::if_break(Doc::text(","), Doc::Nil)); - } - } - - let trailing = self.gap_doc(&self.stream.gaps[close_index], Separator::None); - Ok(Doc::concat([ - open, - Doc::concat([Doc::if_break(Doc::hard_line(), Doc::Nil), Doc::concat(docs)]).indent(), - trailing, - Doc::if_break(Doc::hard_line(), Doc::Nil), - close, - ]) - .group()) - } - - fn token(&mut self, index: usize) -> Doc { - self.consumed_tokens += 1; - Doc::text(self.stream.tokens[index].text.clone()) - } - - fn separator(&self, previous: usize, next: usize) -> Separator { - let left = &self.stream.tokens[previous]; - let right = &self.stream.tokens[next]; - - if self.layout.item_ends.contains(&left.start) { - return Separator::Empty; - } - if self.layout.module_item_ends.contains(&left.start) { - return Separator::Hard; - } - if self.layout.block_item_ends.contains(&left.start) { - return Separator::Hard; - } - - if left.kind == T![;] { - return Separator::Hard; - } - if left.kind == T![,] { - return Separator::Soft; - } - if right.kind == T![::] - && self.layout.absolute_path_starts.contains(&right.start) - && !no_space_after(left.kind) - { - return Separator::Space; - } - if no_space_after(left.kind) || no_space_before(right.kind) { - return Separator::None; - } - if SyntaxKind::BINARY_OPS.contains(&left.kind) - && !self.layout.generic_tokens.contains(&left.start) - && !self.layout.prefix_operators.contains(&left.start) - { - return Separator::Soft; - } - if right.kind == T!['('] && self.layout.attached_openers.contains(&right.start) { - return Separator::None; - } - if left.kind == T!['}'] && right.kind == T![else] { - return Separator::Space; - } - - if ((left.kind == T![<] - || right.kind == T![<] - || right.kind == T![>] - || (left.kind == T![>] && right.kind == T!['('])) - && (self.layout.generic_tokens.contains(&left.start) - || self.layout.generic_tokens.contains(&right.start))) - || (SyntaxKind::PREFIX_OPS.contains(&left.kind) - && self.layout.prefix_operators.contains(&left.start)) - { - Separator::None - } else { - Separator::Space - } - } - - fn gap_doc(&mut self, gap: &Gap, requested: Separator) -> Doc { - self.gap_doc_with_comments(gap, requested, false) - } - - fn gap_doc_with_comments( - &mut self, - gap: &Gap, - requested: Separator, - ignore_trailing: bool, - ) -> Doc { - if gap.comments.is_empty() { - if gap.newlines > 1 && requested != Separator::None { - return Doc::empty_line(); - } - return separator_doc(requested); - } - - let mut docs = Vec::new(); - for (index, comment) in gap.comments.iter().enumerate() { - self.consumed_comments += 1; - let before = if index == 0 { - comment_separator(comment, requested, ignore_trailing) - } else { - comment_separator(comment, Separator::Hard, false) - }; - docs.push(before); - docs.push(Doc::text(comment.text.clone())); - - let followed_inline = match gap.comments.get(index + 1) { - Some(next) => next.newlines_before == 0, - None => gap.newlines == 0, - }; - if comment.kind == SyntaxKind::BlockComment && !comment.multiline && followed_inline { - docs.push(Doc::space()); - } - } - - let last = gap.comments.last().expect("comments are not empty"); - if gap.newlines > 1 { - docs.push(Doc::empty_line()); - } else if last.kind == SyntaxKind::LineComment || last.multiline || gap.newlines > 0 { - docs.push(Doc::hard_line()); - } - Doc::concat(docs) - } -} - -impl Layout { - fn new(document: &AstDocument, stream: &TokenStream) -> Result { - let mut pairs = HashMap::new(); - let mut stack: Vec<(SyntaxKind, usize)> = Vec::new(); - for (index, token) in stream.tokens.iter().enumerate() { - match token.kind { - T!['('] | T!['['] | T!['{'] => stack.push((token.kind, index)), - T![')'] | T![']'] | T!['}'] => { - let Some((open, open_index)) = stack.pop() else { - return Err(FormatError::Internal( - "closing delimiter has no opener".to_string(), - )); - }; - if !delimiters_match(open, token.kind) { - return Err(FormatError::Internal( - "mismatched delimiters in valid syntax".to_string(), - )); - } - pairs.insert(open_index, index); - } - _ => {} - } - } - if !stack.is_empty() { - return Err(FormatError::Internal( - "opening delimiter has no closer".to_string(), - )); - } - - let root = document.syntax(); - let mut block_openers = HashSet::new(); - let mut braced_openers = HashSet::new(); - let mut trailing_comma_openers = HashSet::new(); - let mut generic_tokens = HashSet::new(); - let mut generic_pairs = Vec::new(); - let mut prefix_operators = HashSet::new(); - let mut attached_openers = HashSet::new(); - - for node in root.descendants() { - match node.kind() { - SyntaxKind::Block | SyntaxKind::ModuleItem | SyntaxKind::StructItem => { - if let Some(token) = - significant_tokens(&node).find(|token| token.kind() == T!['{']) - { - let start = usize::from(token.text_range().start()); - block_openers.insert(start); - if node.kind() == SyntaxKind::StructItem { - trailing_comma_openers.insert(start); - } - } - } - SyntaxKind::StructInitializerExpr | SyntaxKind::StructBinding => { - if let Some(token) = - significant_tokens(&node).find(|token| token.kind() == T!['{']) - { - let start = usize::from(token.text_range().start()); - braced_openers.insert(start); - trailing_comma_openers.insert(start); - } - } - SyntaxKind::PairExpr - | SyntaxKind::PairType - | SyntaxKind::PairBinding - | SyntaxKind::ListExpr - | SyntaxKind::ListType - | SyntaxKind::ListBinding - | SyntaxKind::ImportPathSegment => { - if let Some(token) = node - .children_with_tokens() - .filter_map(rowan::NodeOrToken::into_token) - .find(|token| matches!(token.kind(), T!['('] | T!['['] | T!['{'])) - { - trailing_comma_openers.insert(usize::from(token.text_range().start())); - } - } - SyntaxKind::GenericParameters | SyntaxKind::GenericArguments => { - generic_tokens.extend( - significant_tokens(&node) - .filter(|token| is_generic_punctuation(token.kind())) - .map(|token| usize::from(token.text_range().start())), - ); - let direct_tokens: Vec<_> = node - .children_with_tokens() - .filter_map(rowan::NodeOrToken::into_token) - .filter(|token| !token.kind().is_trivia()) - .collect(); - if let (Some(open), Some(close)) = (direct_tokens.first(), direct_tokens.last()) - && open.kind() == T![<] - && close.kind() == T![>] - { - let open = usize::from(open.text_range().start()); - let close = usize::from(close.text_range().start()); - trailing_comma_openers.insert(open); - generic_pairs.push((open, close)); - } - } - SyntaxKind::PrefixExpr => { - if let Some(token) = significant_tokens(&node) - .find(|token| SyntaxKind::PREFIX_OPS.contains(&token.kind())) - { - prefix_operators.insert(usize::from(token.text_range().start())); - } - } - SyntaxKind::FunctionItem - | SyntaxKind::FunctionCallExpr - | SyntaxKind::LambdaExpr - | SyntaxKind::LambdaType => { - if let Some(token) = node - .children_with_tokens() - .filter_map(rowan::NodeOrToken::into_token) - .find(|token| token.kind() == T!['(']) - { - let start = usize::from(token.text_range().start()); - attached_openers.insert(start); - trailing_comma_openers.insert(start); - } - } - _ => {} - } - } - - for (open, close) in generic_pairs { - let Some(open) = stream.tokens.iter().position(|token| token.start == open) else { - continue; - }; - let Some(close) = stream.tokens.iter().position(|token| token.start == close) else { - continue; - }; - pairs.insert(open, close); - } - - let mut item_ends = HashSet::new(); - for item in document.items() { - if let Some(token) = significant_tokens(item.syntax()).last() { - let token_start = usize::from(token.text_range().start()); - let item_end = stream - .tokens - .iter() - .position(|candidate| candidate.start == token_start) - .and_then(|index| stream.tokens.get(index + 1)) - .filter(|next| next.kind == T![;]) - .map_or(token_start, |semicolon| semicolon.start); - item_ends.insert(item_end); - } - } - let mut module_item_ends = HashSet::new(); - for module in root - .descendants() - .filter(|node| node.kind() == SyntaxKind::ModuleItem) - { - for item in module.children() { - if let Some(token) = significant_tokens(&item).last() { - module_item_ends.insert(usize::from(token.text_range().start())); - } - } - } - let absolute_path_starts = root - .descendants() - .filter(|node| { - matches!( - node.kind(), - SyntaxKind::PathExpr | SyntaxKind::PathType | SyntaxKind::ImportPath - ) - }) - .filter_map(|node| significant_tokens(&node).next()) - .filter(|token| token.kind() == T![::]) - .map(|token| usize::from(token.text_range().start())) - .collect(); - let mut block_item_ends = HashSet::new(); - for block in root - .descendants() - .filter(|node| node.kind() == SyntaxKind::Block) - { - for item in block.children() { - if let Some(token) = significant_tokens(&item).last() { - block_item_ends.insert(usize::from(token.text_range().start())); - } - } - } - - let token_indices: HashMap = stream - .tokens - .iter() - .enumerate() - .map(|(index, token)| (token.start, index)) - .collect(); - let mut import_groups = HashMap::new(); - for node in root - .descendants() - .filter(|node| node.kind() == SyntaxKind::ImportPathSegment) - { - let Some(open) = node - .children_with_tokens() - .filter_map(rowan::NodeOrToken::into_token) - .find(|token| token.kind() == T!['{']) - .and_then(|token| { - token_indices - .get(&usize::from(token.text_range().start())) - .copied() - }) - else { - continue; - }; - let mut items = Vec::new(); - for path in node - .children() - .filter(|child| child.kind() == SyntaxKind::ImportPath) - { - let significant: Vec<_> = significant_tokens(&path).collect(); - let (Some(first), Some(last)) = (significant.first(), significant.last()) else { - continue; - }; - let Some(&start) = token_indices.get(&usize::from(first.text_range().start())) - else { - continue; - }; - let Some(&last) = token_indices.get(&usize::from(last.text_range().start())) else { - continue; - }; - let end = last + 1; - let sort_key = stream.tokens[start..end] - .iter() - .map(|token| token.text.as_str()) - .collect(); - items.push(ImportGroupItem { - start, - end, - sort_key, - }); - } - items.sort_by(|left, right| left.sort_key.cmp(&right.sort_key)); - if !items.is_empty() { - import_groups.insert(open, items); - } - } - let mut groups = HashMap::new(); - let mut binary_operators = HashMap::new(); - for node in root - .descendants() - .filter(|node| matches!(node.kind(), SyntaxKind::BinaryExpr | SyntaxKind::UnionType)) - .filter(|node| { - node.parent() - .is_none_or(|parent| parent.kind() != node.kind()) - }) - { - let mut tokens = significant_tokens(&node); - let Some(first) = tokens.next() else { - continue; - }; - let last = tokens.last().unwrap_or_else(|| first.clone()); - let Some(&start) = token_indices.get(&usize::from(first.text_range().start())) else { - continue; - }; - let Some(&end) = token_indices.get(&usize::from(last.text_range().start())) else { - continue; - }; - groups - .entry(start) - .and_modify(|current: &mut usize| *current = (*current).max(end)) - .or_insert(end); - let mut operators = Vec::new(); - if node.kind() == SyntaxKind::BinaryExpr { - collect_binary_operator_starts(&node, &mut operators); - } else { - collect_union_operator_starts(&node, &mut operators); - } - if !operators.is_empty() { - binary_operators.insert( - start, - operators - .into_iter() - .filter_map(|operator| token_indices.get(&operator).copied()) - .collect(), - ); - } - } - - let mut document_items = Vec::new(); - let mut next_import_group = 0; - let mut previous_was_import = false; - for (original_index, item) in document.items().enumerate() { - let significant: Vec<_> = significant_tokens(item.syntax()).collect(); - let (Some(first), Some(last)) = (significant.first(), significant.last()) else { - continue; - }; - let Some(&start) = token_indices.get(&usize::from(first.text_range().start())) else { - continue; - }; - let Some(&last) = token_indices.get(&usize::from(last.text_range().start())) else { - continue; - }; - let mut end = last + 1; - if stream - .tokens - .get(end) - .is_some_and(|token| token.kind == T![;]) - { - end += 1; - } - - let is_import = matches!(&item, AstItem::ImportItem(_)); - let import_group = is_import.then(|| { - if !previous_was_import || (start > 0 && gap_has_blank_line(&stream.gaps[start])) { - next_import_group += 1; - } - next_import_group - }); - previous_was_import = is_import; - let compact_group = matches!( - item.syntax().kind(), - SyntaxKind::ConstantItem | SyntaxKind::TypeAliasItem - ) - .then_some(item.syntax().kind()) - .filter(|_| !item.syntax().text().to_string().contains('\n')); - - let path_key = stream.tokens[start..end] - .iter() - .filter(|token| !matches!(token.kind, T![import] | T![export] | T![;])) - .map(|token| token.text.as_str()) - .collect::(); - let keyword_key = stream.tokens[start..end] - .first() - .map_or("", |token| token.text.as_str()); - document_items.push(DocumentItem { - start, - end, - import_group, - compact_group, - sort_key: format!("{path_key}\0{keyword_key}"), - original_index, - }); - } - document_items.sort_by( - |left, right| match (left.import_group, right.import_group) { - (Some(left_group), Some(right_group)) => left_group - .cmp(&right_group) - .then_with(|| left.sort_key.cmp(&right.sort_key)) - .then_with(|| left.original_index.cmp(&right.original_index)), - (Some(_), None) => std::cmp::Ordering::Less, - (None, Some(_)) => std::cmp::Ordering::Greater, - (None, None) => left.original_index.cmp(&right.original_index), - }, - ); - - Ok(Self { - pairs, - block_openers, - braced_openers, - trailing_comma_openers, - generic_tokens, - prefix_operators, - attached_openers, - absolute_path_starts, - item_ends, - module_item_ends, - block_item_ends, - groups, - binary_operators, - document_items, - import_groups, - }) - } -} - -fn gap_has_blank_line(gap: &Gap) -> bool { - gap.newlines > 1 - || gap - .comments - .iter() - .any(|comment| comment.newlines_before > 1) -} - -fn collect_binary_operator_starts(node: &SyntaxNode, operators: &mut Vec) { - for element in node.children_with_tokens() { - match element { - rowan::NodeOrToken::Node(child) if child.kind() == SyntaxKind::BinaryExpr => { - collect_binary_operator_starts(&child, operators); - } - rowan::NodeOrToken::Token(token) if SyntaxKind::BINARY_OPS.contains(&token.kind()) => { - operators.push(usize::from(token.text_range().start())); - } - _ => {} - } - } -} - -fn collect_union_operator_starts(node: &SyntaxNode, operators: &mut Vec) { - for element in node.children_with_tokens() { - match element { - rowan::NodeOrToken::Node(child) if child.kind() == SyntaxKind::UnionType => { - collect_union_operator_starts(&child, operators); - } - rowan::NodeOrToken::Token(token) if token.kind() == T![|] => { - operators.push(usize::from(token.text_range().start())); - } - _ => {} - } - } -} - -fn significant_tokens(node: &SyntaxNode) -> impl Iterator + '_ { - node.descendants_with_tokens() - .filter_map(rowan::NodeOrToken::into_token) - .filter(|token| !token.kind().is_trivia()) -} - -fn separator_doc(separator: Separator) -> Doc { - match separator { - Separator::None => Doc::Nil, - Separator::Space => Doc::space(), - Separator::Soft => Doc::soft_line(), - Separator::Hard => Doc::hard_line(), - Separator::Empty => Doc::empty_line(), - } -} - -fn comment_separator(comment: &Comment, requested: Separator, ignore_trailing: bool) -> Doc { - if comment.trailing && !ignore_trailing { - return Doc::space(); - } - if comment.newlines_before > 1 { - return Doc::empty_line(); - } - if comment.newlines_before > 0 || comment.multiline { - return Doc::hard_line(); - } - separator_doc(match requested { - Separator::None => Separator::None, - requested => requested, - }) -} - -fn no_space_after(kind: SyntaxKind) -> bool { - matches!(kind, T!['('] | T!['['] | T![::] | T![.] | T![...] | T![,]) -} - -fn no_space_before(kind: SyntaxKind) -> bool { - matches!( - kind, - T![')'] | T![']'] | T![,] | T![;] | T![::] | T![.] | T![:] - ) -} - -fn is_generic_punctuation(kind: SyntaxKind) -> bool { - matches!(kind, T![<] | T![>] | T![,]) -} - -fn is_comparison_operator(kind: SyntaxKind) -> bool { - matches!(kind, T![==] | T![!=] | T![<] | T![>] | T![<=] | T![>=]) -} - -fn delimiters_match(open: SyntaxKind, close: SyntaxKind) -> bool { - matches!( - (open, close), - (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) - ) -} - -fn validate_node_kinds(root: &SyntaxNode) -> Result<(), FormatError> { - for node in root.descendants() { - match node.kind() { - SyntaxKind::Document - | SyntaxKind::ModuleItem - | SyntaxKind::FunctionItem - | SyntaxKind::FunctionParameter - | SyntaxKind::ConstantItem - | SyntaxKind::TypeAliasItem - | SyntaxKind::StructItem - | SyntaxKind::StructField - | SyntaxKind::ImportItem - | SyntaxKind::ImportPath - | SyntaxKind::ImportPathSegment - | SyntaxKind::GenericParameters - | SyntaxKind::GenericArguments - | SyntaxKind::LiteralType - | SyntaxKind::PathType - | SyntaxKind::UnionType - | SyntaxKind::GroupType - | SyntaxKind::PairType - | SyntaxKind::ListType - | SyntaxKind::ListTypeItem - | SyntaxKind::LambdaType - | SyntaxKind::LambdaParameter - | SyntaxKind::Block - | SyntaxKind::LetStmt - | SyntaxKind::ExprStmt - | SyntaxKind::IfStmt - | SyntaxKind::ReturnStmt - | SyntaxKind::AssertStmt - | SyntaxKind::RaiseStmt - | SyntaxKind::DebugStmt - | SyntaxKind::PathExpr - | SyntaxKind::PathSegment - | SyntaxKind::StructInitializerExpr - | SyntaxKind::StructInitializerField - | SyntaxKind::LiteralExpr - | SyntaxKind::ConstExpr - | SyntaxKind::GroupExpr - | SyntaxKind::PairExpr - | SyntaxKind::ListExpr - | SyntaxKind::ListItem - | SyntaxKind::PrefixExpr - | SyntaxKind::BinaryExpr - | SyntaxKind::FunctionCallExpr - | SyntaxKind::IfExpr - | SyntaxKind::GuardExpr - | SyntaxKind::CastExpr - | SyntaxKind::FieldAccessExpr - | SyntaxKind::LambdaExpr - | SyntaxKind::NamedBinding - | SyntaxKind::PairBinding - | SyntaxKind::ListBinding - | SyntaxKind::ListBindingItem - | SyntaxKind::StructBinding - | SyntaxKind::StructFieldBinding => {} - kind => return Err(FormatError::UnsupportedSyntax(kind)), - } - } - Ok(()) + Formatter::new(stream, layout).format() } diff --git a/crates/rue-formatter/src/lib.rs b/crates/rue-formatter/src/lib.rs index 602f0701..b989b6ce 100644 --- a/crates/rue-formatter/src/lib.rs +++ b/crates/rue-formatter/src/lib.rs @@ -8,10 +8,16 @@ //! Configuration files, range formatting, malformed-tree formatting, comment //! reflow, CLI integration, and LSP integration are intentionally deferred. +mod analysis; mod document; +mod emit; +mod equivalence; mod format; +mod ordering; mod renderer; +mod syntax; mod token_stream; +mod trivia; use std::sync::Arc; @@ -21,7 +27,10 @@ use rue_lexer::Lexer; use rue_parser::{Parser, SyntaxKind, SyntaxNode}; use thiserror::Error; -use crate::{format::format_document, renderer::render, token_stream::TokenStream}; +use crate::{ + equivalence::comment_signature, format::format_document, renderer::render, + token_stream::TokenStream, +}; /// Deterministic formatter settings. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -80,9 +89,9 @@ pub fn format_source(source: &str, options: &FormatOptions) -> Result Result<(), FormatError let before_stream = TokenStream::from_syntax(before.document.syntax())?; let after_stream = TokenStream::from_syntax(after.document.syntax())?; - let before_comments = comment_signature(&before_stream); - let after_comments = comment_signature(&after_stream); + let before_comments = comment_signature(&before.document, &before_stream)?; + let after_comments = comment_signature(&after.document, &after_stream)?; if before_comments != after_comments { - return Err(FormatError::Internal( - "formatted source changed or duplicated comments".to_string(), - )); + return Err(FormatError::Internal(format!( + "formatted source changed or duplicated comments\nbefore: {before_comments:#?}\nafter: {after_comments:#?}" + ))); } Ok(()) } @@ -246,19 +255,5 @@ fn is_optional_trailing_comma(token: &rue_parser::SyntaxToken) -> bool { }) } -fn comment_signature(stream: &TokenStream) -> Vec<(SyntaxKind, &str)> { - let mut signature: Vec<_> = stream - .gaps - .iter() - .flat_map(|gap| { - gap.comments - .iter() - .map(|comment| (comment.kind, comment.text.as_str())) - }) - .collect(); - signature.sort_unstable(); - signature -} - #[cfg(test)] mod tests; diff --git a/crates/rue-formatter/src/ordering.rs b/crates/rue-formatter/src/ordering.rs new file mode 100644 index 00000000..08cd3f2b --- /dev/null +++ b/crates/rue-formatter/src/ordering.rs @@ -0,0 +1,325 @@ +use std::{collections::HashMap, fmt::Write}; + +use rue_ast::{AstDocument, AstItem, AstNode}; +use rue_parser::{SyntaxKind, SyntaxNode, T}; + +use crate::{ + FormatError, + token_stream::{TokenId, TokenSpan, TokenStream}, + trivia::{Trivia, split_between, split_file_header}, +}; + +#[derive(Debug, Clone)] +pub(crate) struct DocumentItem { + pub(crate) span: TokenSpan, + pub(crate) import_group: Option, + pub(crate) compact_group: Option, + pub(crate) leading: Trivia, + pub(crate) trailing: Trivia, + pub(crate) identity_key: String, + sort_key: String, + original_index: usize, +} + +#[derive(Debug)] +pub(crate) struct DocumentPlan { + pub(crate) header: Trivia, + pub(crate) items: Vec, + pub(crate) footer: Trivia, +} + +#[derive(Debug, Clone)] +pub(crate) struct ImportPathItem { + pub(crate) span: TokenSpan, + pub(crate) comma: Option, + pub(crate) leading: Trivia, + pub(crate) before_comma: Trivia, + pub(crate) trailing: Trivia, + pub(crate) identity_key: String, + sort_key: String, + original_index: usize, +} + +#[derive(Debug, Clone)] +pub(crate) struct ImportGroupPlan { + pub(crate) opening: Trivia, + pub(crate) items: Vec, + pub(crate) closing: Trivia, +} + +pub(crate) fn plan_document( + document: &AstDocument, + stream: &TokenStream, +) -> Result { + let mut items = Vec::new(); + let mut next_import_group = 0; + let mut previous_was_import = false; + + for (original_index, item) in document.items().enumerate() { + let span = item_span(item.syntax(), stream)?; + let is_import = matches!(&item, AstItem::ImportItem(_)); + let import_group = is_import.then(|| { + if !previous_was_import + || (span.start.index() > 0 && gap_has_blank_line(stream.gap_before(span.start))) + { + next_import_group += 1; + } + next_import_group + }); + previous_was_import = is_import; + + let compact_group = matches!( + item.syntax().kind(), + SyntaxKind::ConstantItem | SyntaxKind::TypeAliasItem + ) + .then_some(item.syntax().kind()) + .filter(|_| !item.syntax().text().to_string().contains('\n')); + let path_key = stream.tokens[span.start.index()..span.end.index()] + .iter() + .filter(|token| !matches!(token.kind, T![import] | T![export] | T![;])) + .map(|token| token.text.as_str()) + .collect::(); + let keyword_key = stream + .tokens + .get(span.start.index()) + .map_or("", |token| token.text.as_str()); + + items.push(DocumentItem { + span, + import_group, + compact_group, + leading: Trivia::default(), + trailing: Trivia::default(), + identity_key: canonical_node_key(item.syntax()), + sort_key: format!("{path_key}\0{keyword_key}"), + original_index, + }); + } + + if items.is_empty() { + return Ok(DocumentPlan { + header: Trivia::new(stream.gaps[0].clone()), + items, + footer: Trivia::default(), + }); + } + + let (header, first_leading) = split_file_header(stream.gap_before(items[0].span.start)); + items[0].leading = first_leading; + for index in 1..items.len() { + let (trailing, leading) = split_between(stream.gap_before(items[index].span.start)); + items[index - 1].trailing = trailing; + items[index].leading = leading; + } + let last = items + .last_mut() + .expect("non-empty document plan has a final item"); + let (trailing, footer) = split_between( + stream + .gaps + .last() + .expect("a token stream always has a final gap"), + ); + last.trailing = trailing; + + items.sort_by( + |left, right| match (left.import_group, right.import_group) { + (Some(left_group), Some(right_group)) => left_group + .cmp(&right_group) + .then_with(|| left.sort_key.cmp(&right.sort_key)) + .then_with(|| left.original_index.cmp(&right.original_index)), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => left.original_index.cmp(&right.original_index), + }, + ); + + Ok(DocumentPlan { + header, + items, + footer, + }) +} + +pub(crate) fn plan_import_groups( + root: &SyntaxNode, + stream: &TokenStream, +) -> Result, FormatError> { + let mut groups = HashMap::new(); + for node in root + .descendants() + .filter(|node| node.kind() == SyntaxKind::ImportPathSegment) + { + let Some(open_token) = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .find(|token| token.kind() == T!['{']) + else { + continue; + }; + let open = stream.token_id_at_offset(usize::from(open_token.text_range().start()))?; + let close = find_matching_close(open, stream)?; + let paths: Vec<_> = node + .children() + .filter(|child| child.kind() == SyntaxKind::ImportPath) + .collect(); + if paths.is_empty() { + continue; + } + + let mut items = Vec::new(); + for (original_index, path) in paths.iter().enumerate() { + let span = node_span(path, stream)?; + let comma = stream + .tokens + .get(span.end.index()) + .filter(|token| token.kind == T![,]) + .map(|_| span.end); + let sort_key = stream.tokens[span.start.index()..span.end.index()] + .iter() + .map(|token| token.text.as_str()) + .collect(); + let before_comma = comma + .map(|comma| Trivia::new(stream.gap_before(comma).clone())) + .unwrap_or_default(); + items.push(ImportPathItem { + span, + comma, + leading: Trivia::default(), + before_comma, + trailing: Trivia::default(), + identity_key: canonical_node_key(path), + sort_key, + original_index, + }); + } + + let (opening, first_leading) = split_between(stream.gap_before(items[0].span.start)); + items[0].leading = first_leading; + for index in 1..items.len() { + let boundary = stream.gap_before(items[index].span.start); + let (trailing, leading) = split_between(boundary); + items[index - 1].trailing = trailing; + items[index].leading = leading; + } + let final_boundary = stream.gap_before(close); + let (trailing, closing) = split_between(final_boundary); + items + .last_mut() + .expect("non-empty import group has a final path") + .trailing = trailing; + + items.sort_by(|left, right| { + left.sort_key + .cmp(&right.sort_key) + .then_with(|| left.original_index.cmp(&right.original_index)) + }); + groups.insert( + open, + ImportGroupPlan { + opening: Trivia::dangling(opening.gap), + items, + closing: Trivia::dangling(closing.gap), + }, + ); + } + Ok(groups) +} + +fn item_span(node: &SyntaxNode, stream: &TokenStream) -> Result { + let mut span = node_span(node, stream)?; + if stream + .tokens + .get(span.end.index()) + .is_some_and(|token| token.kind == T![;]) + { + span.end = span.end.next(); + } + Ok(span) +} + +fn node_span(node: &SyntaxNode, stream: &TokenStream) -> Result { + let mut tokens = significant_tokens(node); + let first = tokens.next().ok_or_else(|| { + FormatError::Internal(format!("{:?} node has no significant tokens", node.kind())) + })?; + let last = tokens.last().unwrap_or_else(|| first.clone()); + let start = stream.token_id_at_offset(usize::from(first.text_range().start()))?; + let end = stream + .token_id_at_offset(usize::from(last.text_range().start()))? + .next(); + Ok(TokenSpan::new(start, end)) +} + +fn find_matching_close(open: TokenId, stream: &TokenStream) -> Result { + let mut depth = 0; + for index in open.index()..stream.tokens.len() { + match stream.tokens[index].kind { + T!['{'] => depth += 1, + T!['}'] => { + depth -= 1; + if depth == 0 { + return Ok(TokenId::new(index)); + } + } + _ => {} + } + } + Err(FormatError::Internal( + "import path group has no closing delimiter".to_string(), + )) +} + +fn gap_has_blank_line(gap: &crate::token_stream::Gap) -> bool { + gap.newlines > 1 + || gap + .comments + .iter() + .any(|comment| comment.newlines_before > 1) +} + +fn significant_tokens(node: &SyntaxNode) -> impl Iterator + '_ { + node.descendants_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .filter(|token| !token.kind().is_trivia()) +} + +fn canonical_node_key(node: &SyntaxNode) -> String { + let mut key = format!("n:{:?}[", node.kind()); + let mut sorted_paths = if node.kind() == SyntaxKind::ImportPathSegment { + let mut paths: Vec<_> = node + .children() + .filter(|child| child.kind() == SyntaxKind::ImportPath) + .map(|child| canonical_node_key(&child)) + .collect(); + paths.sort(); + paths.into_iter() + } else { + Vec::new().into_iter() + }; + + for element in node.children_with_tokens() { + match element { + rowan::NodeOrToken::Node(child) + if node.kind() == SyntaxKind::ImportPathSegment + && child.kind() == SyntaxKind::ImportPath => + { + key.push_str( + &sorted_paths + .next() + .expect("each import path has a canonical identity"), + ); + } + rowan::NodeOrToken::Node(child) => key.push_str(&canonical_node_key(&child)), + rowan::NodeOrToken::Token(token) + if !token.kind().is_trivia() && token.kind() != T![,] => + { + write!(&mut key, "t:{:?}:{};", token.kind(), token.text()) + .expect("writing to a String cannot fail"); + } + rowan::NodeOrToken::Token(_) => {} + } + } + key.push(']'); + key +} diff --git a/crates/rue-formatter/src/renderer.rs b/crates/rue-formatter/src/renderer.rs index 4eabfd9e..fc97d306 100644 --- a/crates/rue-formatter/src/renderer.rs +++ b/crates/rue-formatter/src/renderer.rs @@ -45,12 +45,18 @@ pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { output.push(' '); column += 1; } - Doc::PreferredBreak { + Doc::Fill { doc, indent_on_break, } => { if column < options.max_width - && fits_broken_prefix(options.max_width - column - 1, doc, &commands) + && fits( + options.max_width - column - 1, + command.indent, + doc, + &commands, + Mode::Broken, + ) { output.push(' '); column += 1; @@ -61,12 +67,8 @@ pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { }); } else { output.push('\n'); - let indent = command.indent - + if *indent_on_break { - options.indent_width - } else { - 0 - }; + let indent = + command.indent + usize::from(*indent_on_break) * options.indent_width; output.extend(std::iter::repeat_n(' ', indent)); column = indent; commands.push(Command { @@ -96,6 +98,7 @@ pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { command.indent, doc, &commands, + Mode::Flat, ) { Mode::Broken } else { @@ -128,7 +131,7 @@ fn has_forced_line(doc: &Doc) -> bool { Doc::Line(LineKind::Hard | LineKind::Empty) => true, Doc::Concat(docs) => docs.iter().any(has_forced_line), Doc::Indent(doc) | Doc::Group(doc) => has_forced_line(doc), - Doc::PreferredBreak { doc, .. } => has_forced_line(doc), + Doc::Fill { doc, .. } => has_forced_line(doc), Doc::IfBreak { flat, .. } => has_forced_line(flat), } } @@ -138,11 +141,12 @@ fn fits( initial_indent: usize, doc: &Doc, remaining_commands: &[Command<'_>], + initial_mode: Mode, ) -> bool { let mut commands = remaining_commands.to_vec(); commands.push(Command { indent: initial_indent, - mode: Mode::Flat, + mode: initial_mode, doc, }); @@ -171,14 +175,7 @@ fn fits( } remaining -= 1; } - Doc::Line(LineKind::Soft) => return true, - Doc::Line(LineKind::Hard | LineKind::Empty) => return true, - Doc::Indent(doc) | Doc::Group(doc) => commands.push(Command { - indent: command.indent, - mode: command.mode, - doc, - }), - Doc::PreferredBreak { doc, .. } => { + Doc::Fill { doc, .. } => { if remaining == 0 { return false; } @@ -189,6 +186,13 @@ fn fits( doc, }); } + Doc::Line(LineKind::Soft) => return true, + Doc::Line(LineKind::Hard | LineKind::Empty) => return true, + Doc::Indent(doc) | Doc::Group(doc) => commands.push(Command { + indent: command.indent, + mode: command.mode, + doc, + }), Doc::IfBreak { broken, flat } => commands.push(Command { indent: command.indent, mode: command.mode, @@ -204,51 +208,6 @@ fn fits( true } -fn fits_broken_prefix(mut remaining: usize, doc: &Doc, remaining_commands: &[Command<'_>]) -> bool { - let mut commands = remaining_commands.to_vec(); - commands.push(Command { - indent: 0, - mode: Mode::Broken, - doc, - }); - - while let Some(command) = commands.pop() { - match command.doc { - Doc::Nil => {} - Doc::Text(text) => { - let width = text.chars().count(); - if width > remaining { - return false; - } - remaining -= width; - } - Doc::Concat(docs) => { - for doc in docs.iter().rev() { - commands.push(Command { - indent: command.indent, - mode: Mode::Broken, - doc, - }); - } - } - Doc::Line(_) => return true, - Doc::Indent(doc) | Doc::Group(doc) => commands.push(Command { - indent: command.indent, - mode: Mode::Broken, - doc, - }), - Doc::PreferredBreak { .. } => return true, - Doc::IfBreak { broken, .. } => commands.push(Command { - indent: command.indent, - mode: Mode::Broken, - doc: broken, - }), - } - } - - true -} - fn normalize_output(output: &str) -> String { let mut normalized = output.to_string(); @@ -262,3 +221,48 @@ fn normalize_output(output: &str) -> String { normalized } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fill_line_stays_flat_before_broken_suffix_when_prefix_fits() { + let doc = Doc::concat([ + Doc::text("lhs"), + Doc::fill(Doc::text("== rhs"), false), + Doc::hard_line(), + Doc::text("tail"), + ]); + assert_eq!( + render( + &doc, + &FormatOptions { + max_width: 10, + indent_width: 4, + }, + ), + "lhs == rhs\ntail\n" + ); + } + + #[test] + fn fill_line_breaks_when_broken_prefix_does_not_fit() { + let doc = Doc::concat([ + Doc::text("long_lhs"), + Doc::fill(Doc::text("== rhs"), true), + Doc::hard_line(), + Doc::text("tail"), + ]); + assert_eq!( + render( + &doc, + &FormatOptions { + max_width: 10, + indent_width: 4, + }, + ), + "long_lhs\n == rhs\ntail\n" + ); + } +} diff --git a/crates/rue-formatter/src/syntax.rs b/crates/rue-formatter/src/syntax.rs new file mode 100644 index 00000000..85726950 --- /dev/null +++ b/crates/rue-formatter/src/syntax.rs @@ -0,0 +1,66 @@ +use rue_parser::{SyntaxKind, SyntaxNode}; + +use crate::FormatError; + +pub(crate) fn validate_node_kinds(root: &SyntaxNode) -> Result<(), FormatError> { + for node in root.descendants() { + match node.kind() { + SyntaxKind::Document + | SyntaxKind::ModuleItem + | SyntaxKind::FunctionItem + | SyntaxKind::FunctionParameter + | SyntaxKind::ConstantItem + | SyntaxKind::TypeAliasItem + | SyntaxKind::StructItem + | SyntaxKind::StructField + | SyntaxKind::ImportItem + | SyntaxKind::ImportPath + | SyntaxKind::ImportPathSegment + | SyntaxKind::GenericParameters + | SyntaxKind::GenericArguments + | SyntaxKind::LiteralType + | SyntaxKind::PathType + | SyntaxKind::UnionType + | SyntaxKind::GroupType + | SyntaxKind::PairType + | SyntaxKind::ListType + | SyntaxKind::ListTypeItem + | SyntaxKind::LambdaType + | SyntaxKind::LambdaParameter + | SyntaxKind::Block + | SyntaxKind::LetStmt + | SyntaxKind::ExprStmt + | SyntaxKind::IfStmt + | SyntaxKind::ReturnStmt + | SyntaxKind::AssertStmt + | SyntaxKind::RaiseStmt + | SyntaxKind::DebugStmt + | SyntaxKind::PathExpr + | SyntaxKind::PathSegment + | SyntaxKind::StructInitializerExpr + | SyntaxKind::StructInitializerField + | SyntaxKind::LiteralExpr + | SyntaxKind::ConstExpr + | SyntaxKind::GroupExpr + | SyntaxKind::PairExpr + | SyntaxKind::ListExpr + | SyntaxKind::ListItem + | SyntaxKind::PrefixExpr + | SyntaxKind::BinaryExpr + | SyntaxKind::FunctionCallExpr + | SyntaxKind::IfExpr + | SyntaxKind::GuardExpr + | SyntaxKind::CastExpr + | SyntaxKind::FieldAccessExpr + | SyntaxKind::LambdaExpr + | SyntaxKind::NamedBinding + | SyntaxKind::PairBinding + | SyntaxKind::ListBinding + | SyntaxKind::ListBindingItem + | SyntaxKind::StructBinding + | SyntaxKind::StructFieldBinding => {} + kind => return Err(FormatError::UnsupportedSyntax(kind)), + } + } + Ok(()) +} diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index 9b737370..d098cbda 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -2,6 +2,9 @@ use expect_test::{Expect, expect}; use crate::{FormatError, FormatOptions, format_source}; +mod comments; +mod imports; + #[allow(clippy::needless_pass_by_value)] fn check(input: &str, expected: Expect) { let output = format_source(input, &FormatOptions::default()).expect("source should format"); @@ -42,46 +45,6 @@ fn types_bindings_generics_and_imports() { ); } -#[test] -fn comments_are_preserved_once() { - check( - "// lead\nfn main(/* a */x:Int){// before\nlet y=x/* op */+1; // trailing\n// tail\ny}", - expect![[r#" - // lead - fn main(/* a */ x: Int) { - // before - let y = x /* op */ + 1; // trailing - // tail - y - } - "#]], - ); -} - -#[test] -fn comment_position_matrix() { - check( - "/* file */\nfn main(/* parameter */x:Int)->Int{let y=[/* dangling */];x/* left */+/* right */1}// eof", - expect![[r#" - /* file */ - fn main(/* parameter */ x: Int) -> Int { - let y = [ /* dangling */ ]; - x /* left */ + /* right */ 1 - } // eof - "#]], - ); -} - -#[test] -fn multiline_comment_text_is_exact() { - let output = format_source( - "fn main(){/* first \nsecond */1}", - &FormatOptions::default(), - ) - .unwrap(); - assert!(output.contains("/* first \nsecond */")); -} - #[test] fn preserves_one_intentional_blank_line() { check( @@ -364,34 +327,6 @@ fn union_type_operators_lead_continuation_lines() { assert_eq!(format_source(&output, &options).unwrap(), output); } -#[test] -fn imports_are_hoisted_grouped_and_sorted() { - check( - "const VALUE:Int=1; import zebra; import beta::{zeta,alpha};\n\nimport delta; import charlie; fn main(){}", - expect![[r#" - import beta::{alpha, zeta}; - import zebra; - - import charlie; - import delta; - - const VALUE: Int = 1; - - fn main() {} - "#]], - ); - check( - "const VALUE:Int=1;\n// zebra\nimport zebra;\nimport alpha;", - expect![[r#" - import alpha; - // zebra - import zebra; - - const VALUE: Int = 1; - "#]], - ); -} - #[test] fn compact_items_are_grouped() { check( @@ -700,39 +635,6 @@ fn broken_lists_add_trailing_commas_everywhere() { assert_eq!(format_source(&output, &options).unwrap(), output); } -#[test] -fn comment_and_blank_line_boundaries() { - check( - "//! file\n\n/* before */const VALUE:Int=1;/* between */\n\nfn main(){let list=[/* open */1,/* item */2/* close */];let value=1/* before op */+/* after op */2;// trailing\n/* own line */\nvalue}// eof", - expect![[r#" - //! file - - /* before */ const VALUE: Int = 1; /* between */ - - fn main() { - let list = [/* open */ 1, /* item */ 2 /* close */ ]; - let value = 1 /* before op */ + /* after op */ 2; // trailing - /* own line */ - value - } // eof - "#]], - ); -} - -#[test] -fn nested_import_paths_are_sorted_without_crossing_groups() { - check( - "import root::{zeta::{two,one},alpha,super::thing};\nimport beta::*;\n\nexport zebra::{last,first};\nexport alpha;", - expect![[r#" - import beta::*; - import root::{alpha, super::thing, zeta::{one, two}}; - - export alpha; - export zebra::{first, last}; - "#]], - ); -} - #[test] fn custom_indentation_width_is_applied_consistently() { let options = FormatOptions { @@ -824,33 +726,6 @@ fn comparison_operator_wrap_matrix() { assert_eq!(format_source(&output, &options).unwrap(), output); } -#[test] -fn comments_inside_broken_delimiters() { - let options = FormatOptions { - max_width: 32, - ..FormatOptions::default() - }; - let output = format_source( - "fn comments(){consume(first_argument,// first\nsecond_argument,/* third */third_argument);[first_argument,// spread\n...remaining_arguments]}", - &options, - ) - .unwrap(); - expect![[r#" - fn comments() { - consume( - first_argument, // first - second_argument, /* third */ third_argument, - ); - [ - first_argument, // spread - ...remaining_arguments, - ] - } - "#]] - .assert_eq(&output); - assert_eq!(format_source(&output, &options).unwrap(), output); -} - #[test] fn long_types_and_bindings_break_consistently() { let options = FormatOptions { @@ -880,60 +755,6 @@ fn long_types_and_bindings_break_consistently() { assert_eq!(format_source(&output, &options).unwrap(), output); } -#[test] -fn comments_between_if_else_branches_stay_attached() { - check( - "fn choose(value:Int)->Int{if value>0{// positive\n1}else if value<0{/* negative */-1}else{// zero\n0}}", - expect![[r#" - fn choose(value: Int) -> Int { - if value > 0 { - // positive - 1 - } else if value < 0 { - /* negative */ -1 - } else { - // zero - 0 - } - } - "#]], - ); -} - -#[test] -fn import_comments_move_with_their_imports() { - check( - "// zeta docs\nimport zeta;\n// alpha docs\nimport alpha;\n\n// beta export\nexport beta;", - expect![[r#" - // alpha docs - import alpha; - // zeta docs - import zeta; - - // beta export - export beta; - "#]], - ); -} - -#[test] -fn excessive_blank_lines_are_normalized_around_comments() { - check( - "const FIRST:Int=1;\n\n\n// second group\n\n\nconst SECOND:Int=2;\n\n\n\nfn main(){FIRST+SECOND}", - expect![[r#" - const FIRST: Int = 1; - - // second group - - const SECOND: Int = 2; - - fn main() { - FIRST + SECOND - } - "#]], - ); -} - #[test] fn malformed_input_matrix_is_rejected() { for source in [ diff --git a/crates/rue-formatter/src/tests/comments.rs b/crates/rue-formatter/src/tests/comments.rs new file mode 100644 index 00000000..a00a6923 --- /dev/null +++ b/crates/rue-formatter/src/tests/comments.rs @@ -0,0 +1,125 @@ +use super::*; + +#[test] +fn comments_are_preserved_once() { + check( + "// lead\nfn main(/* a */x:Int){// before\nlet y=x/* op */+1; // trailing\n// tail\ny}", + expect![[r#" + // lead + fn main(/* a */ x: Int) { + // before + let y = x /* op */ + 1; // trailing + // tail + y + } + "#]], + ); +} + +#[test] +fn comment_position_matrix() { + check( + "/* file */\nfn main(/* parameter */x:Int)->Int{let y=[/* dangling */];x/* left */+/* right */1}// eof", + expect![[r#" + /* file */ + fn main(/* parameter */ x: Int) -> Int { + let y = [ /* dangling */ ]; + x /* left */ + /* right */ 1 + } // eof + "#]], + ); +} + +#[test] +fn multiline_comment_text_is_exact() { + let output = format_source( + "fn main(){/* first \nsecond */1}", + &FormatOptions::default(), + ) + .unwrap(); + assert!(output.contains("/* first \nsecond */")); +} + +#[test] +fn comment_and_blank_line_boundaries() { + check( + "//! file\n\n/* before */const VALUE:Int=1;/* between */\n\nfn main(){let list=[/* open */1,/* item */2/* close */];let value=1/* before op */+/* after op */2;// trailing\n/* own line */\nvalue}// eof", + expect![[r#" + //! file + + /* before */ const VALUE: Int = 1; /* between */ + + fn main() { + let list = [/* open */ 1, /* item */ 2 /* close */ ]; + let value = 1 /* before op */ + /* after op */ 2; // trailing + /* own line */ + value + } // eof + "#]], + ); +} + +#[test] +fn comments_inside_broken_delimiters() { + let options = FormatOptions { + max_width: 32, + ..FormatOptions::default() + }; + let output = format_source( + "fn comments(){consume(first_argument,// first\nsecond_argument,/* third */third_argument);[first_argument,// spread\n...remaining_arguments]}", + &options, + ) + .unwrap(); + expect![[r#" + fn comments() { + consume( + first_argument, // first + second_argument, /* third */ third_argument, + ); + [ + first_argument, // spread + ...remaining_arguments, + ] + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn comments_between_if_else_branches_stay_attached() { + check( + "fn choose(value:Int)->Int{if value>0{// positive\n1}else if value<0{/* negative */-1}else{// zero\n0}}", + expect![[r#" + fn choose(value: Int) -> Int { + if value > 0 { + // positive + 1 + } else if value < 0 { + /* negative */ -1 + } else { + // zero + 0 + } + } + "#]], + ); +} + +#[test] +fn excessive_blank_lines_are_normalized_around_comments() { + check( + "const FIRST:Int=1;\n\n\n// second group\n\n\nconst SECOND:Int=2;\n\n\n\nfn main(){FIRST+SECOND}", + expect![[r#" + const FIRST: Int = 1; + + // second group + + const SECOND: Int = 2; + + fn main() { + FIRST + SECOND + } + "#]], + ); +} diff --git a/crates/rue-formatter/src/tests/imports.rs b/crates/rue-formatter/src/tests/imports.rs new file mode 100644 index 00000000..d1315e7a --- /dev/null +++ b/crates/rue-formatter/src/tests/imports.rs @@ -0,0 +1,114 @@ +use super::*; + +#[test] +fn imports_are_hoisted_grouped_and_sorted() { + check( + "const VALUE:Int=1; import zebra; import beta::{zeta,alpha};\n\nimport delta; import charlie; fn main(){}", + expect![[r#" + import beta::{alpha, zeta}; + import zebra; + + import charlie; + import delta; + + const VALUE: Int = 1; + + fn main() {} + "#]], + ); + check( + "const VALUE:Int=1;\n// zebra\nimport zebra;\nimport alpha;", + expect![[r#" + import alpha; + // zebra + import zebra; + + const VALUE: Int = 1; + "#]], + ); +} + +#[test] +fn nested_import_paths_are_sorted_without_crossing_groups() { + check( + "import root::{zeta::{two,one},alpha,super::thing};\nimport beta::*;\n\nexport zebra::{last,first};\nexport alpha;", + expect![[r#" + import beta::*; + import root::{alpha, super::thing, zeta::{one, two}}; + + export alpha; + export zebra::{first, last}; + "#]], + ); +} + +#[test] +fn import_comments_move_with_their_imports() { + check( + "// zeta docs\nimport zeta;\n// alpha docs\nimport alpha;\n\n// beta export\nexport beta;", + expect![[r#" + // alpha docs + import alpha; + // zeta docs + import zeta; + + // beta export + export beta; + "#]], + ); +} + +#[test] +fn trailing_comments_stay_with_sorted_imports() { + check( + "import zeta; // zeta tail\n// alpha docs\nimport alpha; // alpha tail", + expect![[r#" + // alpha docs + import alpha; // alpha tail + import zeta; // zeta tail + "#]], + ); +} + +#[test] +fn nested_import_comments_have_stable_owners() { + check( + "// root docs\nimport root::{zeta, // zeta tail\n// alpha docs\nalpha};", + expect![[r#" + // root docs + import root::{ + // alpha docs + alpha, + zeta, // zeta tail + }; + "#]], + ); +} + +#[test] +fn duplicate_identical_comments_are_preserved_per_import() { + check( + "// docs\nimport zeta;\n// docs\nimport alpha;", + expect![[r#" + // docs + import alpha; + // docs + import zeta; + "#]], + ); +} + +#[test] +fn file_header_is_not_first_import_documentation() { + check( + "// file header\n\n// zeta docs\nimport zeta;\n// alpha docs\nimport alpha;", + expect![[r#" + // file header + + // alpha docs + import alpha; + // zeta docs + import zeta; + "#]], + ); +} diff --git a/crates/rue-formatter/src/token_stream.rs b/crates/rue-formatter/src/token_stream.rs index 5529a70e..8761261b 100644 --- a/crates/rue-formatter/src/token_stream.rs +++ b/crates/rue-formatter/src/token_stream.rs @@ -1,7 +1,38 @@ +use std::collections::HashMap; + use rue_parser::{SyntaxKind, SyntaxNode}; use crate::FormatError; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct TokenId(usize); + +impl TokenId { + pub(crate) fn new(index: usize) -> Self { + Self(index) + } + + pub(crate) fn index(self) -> usize { + self.0 + } + + pub(crate) fn next(self) -> Self { + Self(self.0 + 1) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct TokenSpan { + pub(crate) start: TokenId, + pub(crate) end: TokenId, +} + +impl TokenSpan { + pub(crate) fn new(start: TokenId, end: TokenId) -> Self { + Self { start, end } + } +} + #[derive(Debug, Clone)] pub(crate) struct Token { pub(crate) kind: SyntaxKind, @@ -20,15 +51,23 @@ pub(crate) struct Comment { pub(crate) text: String, pub(crate) kind: SyntaxKind, pub(crate) newlines_before: usize, - pub(crate) trailing: bool, + pub(crate) placement: CommentPlacement, pub(crate) multiline: bool, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum CommentPlacement { + Leading, + Trailing, + Dangling, +} + #[derive(Debug, Clone)] pub(crate) struct TokenStream { pub(crate) tokens: Vec, pub(crate) gaps: Vec, pub(crate) comment_count: usize, + token_by_offset: HashMap, } impl TokenStream { @@ -78,7 +117,11 @@ impl TokenStream { text, kind, newlines_before: pending_newlines, - trailing: line_has_significant && pending_newlines == 0, + placement: if line_has_significant && pending_newlines == 0 { + CommentPlacement::Trailing + } else { + CommentPlacement::Leading + }, multiline, }); comment_count += 1; @@ -118,10 +161,33 @@ impl TokenStream { )); } + let token_by_offset = tokens + .iter() + .enumerate() + .map(|(index, token)| (token.start, TokenId::new(index))) + .collect(); + Ok(Self { tokens, gaps, comment_count, + token_by_offset, + }) + } + + pub(crate) fn token(&self, id: TokenId) -> &Token { + &self.tokens[id.index()] + } + + pub(crate) fn gap_before(&self, id: TokenId) -> &Gap { + &self.gaps[id.index()] + } + + pub(crate) fn token_id_at_offset(&self, offset: usize) -> Result { + self.token_by_offset.get(&offset).copied().ok_or_else(|| { + FormatError::Internal(format!( + "syntax token at source offset {offset} has no significant token ID" + )) }) } } diff --git a/crates/rue-formatter/src/trivia.rs b/crates/rue-formatter/src/trivia.rs new file mode 100644 index 00000000..4be3b91e --- /dev/null +++ b/crates/rue-formatter/src/trivia.rs @@ -0,0 +1,85 @@ +use crate::token_stream::{CommentPlacement, Gap}; + +#[derive(Debug, Clone, Default)] +pub(crate) struct Trivia { + pub(crate) gap: Gap, +} + +impl Trivia { + pub(crate) fn new(gap: Gap) -> Self { + Self { gap } + } + + pub(crate) fn dangling(mut gap: Gap) -> Self { + for comment in &mut gap.comments { + comment.placement = CommentPlacement::Dangling; + } + Self { gap } + } +} + +/// Splits trivia between two movable units without guessing that the whole gap +/// belongs to the unit on its right. Inline comments form the trailing prefix; +/// comments beginning on their own line form the leading suffix. +pub(crate) fn split_between(gap: &Gap) -> (Trivia, Trivia) { + let split = gap + .comments + .iter() + .position(|comment| comment.placement != CommentPlacement::Trailing) + .unwrap_or(gap.comments.len()); + + let mut trailing = Gap { + comments: gap.comments[..split].to_vec(), + newlines: 0, + }; + let mut leading = Gap { + comments: gap.comments[split..].to_vec(), + newlines: gap.newlines, + }; + + if trailing.comments.is_empty() { + return (Trivia::default(), Trivia::new(leading)); + } + if leading.comments.is_empty() { + trailing.newlines = gap.newlines; + return (Trivia::new(trailing), Trivia::default()); + } + + let first_leading = leading + .comments + .first_mut() + .expect("leading trivia is known to contain a comment"); + trailing.newlines = first_leading.newlines_before; + first_leading.newlines_before = 0; + (Trivia::new(trailing), Trivia::new(leading)) +} + +/// Separates comments that are visually a file banner from documentation +/// attached to the first item. The closest contiguous comment block is leading +/// trivia; earlier blocks remain anchored at the file head. +pub(crate) fn split_file_header(gap: &Gap) -> (Trivia, Trivia) { + let (_, item_leading) = split_between(gap); + let gap = &item_leading.gap; + if gap.comments.is_empty() || gap.newlines > 1 { + return (Trivia::new(gap.clone()), Trivia::default()); + } + + let mut attached_start = gap.comments.len() - 1; + while attached_start > 0 && gap.comments[attached_start].newlines_before <= 1 { + attached_start -= 1; + } + + let mut header = Gap { + comments: gap.comments[..attached_start].to_vec(), + newlines: 0, + }; + let mut leading = Gap { + comments: gap.comments[attached_start..].to_vec(), + newlines: gap.newlines, + }; + if let Some(first) = leading.comments.first_mut() { + header.newlines = first.newlines_before; + first.newlines_before = 0; + } + (Trivia::new(header), Trivia::new(leading)) +} From 0ab3c22c35d75ee544b25ab9fa6f906201d2561c Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 16:54:40 -0400 Subject: [PATCH 08/21] Refactor --- crates/rue-formatter/src/analysis.rs | 11 +- crates/rue-formatter/src/emit.rs | 111 +++++++++---- crates/rue-formatter/src/equivalence.rs | 10 +- crates/rue-formatter/src/ordering.rs | 52 +++--- crates/rue-formatter/src/tests/imports.rs | 18 +++ crates/rue-formatter/src/token_stream.rs | 185 ++++++++++++++++++++-- crates/rue-formatter/src/trivia.rs | 35 ++++ 7 files changed, 337 insertions(+), 85 deletions(-) diff --git a/crates/rue-formatter/src/analysis.rs b/crates/rue-formatter/src/analysis.rs index d240b5a5..e04b5b24 100644 --- a/crates/rue-formatter/src/analysis.rs +++ b/crates/rue-formatter/src/analysis.rs @@ -102,7 +102,7 @@ impl Layout { fn pair_delimiters(stream: &TokenStream, facts: &mut [TokenFacts]) -> Result<(), FormatError> { let mut stack: Vec<(SyntaxKind, TokenId)> = Vec::new(); for (index, token) in stream.tokens.iter().enumerate() { - let id = TokenId::new(index); + let id = stream.token_id(index)?; match token.kind { T!['('] | T!['['] | T!['{'] => stack.push((token.kind, id)), T![')'] | T![']'] | T!['}'] => { @@ -238,12 +238,11 @@ fn analyze_boundaries( )); }; let mut id = token_id(&token, stream)?; - if stream - .tokens - .get(id.next().index()) - .is_some_and(|token| token.kind == T![;]) + if let Some(next) = stream + .next_token(id) + .filter(|next| stream.token(*next).kind == T![;]) { - id = id.next(); + id = next; } facts[id.index()].item_boundary = Some(ItemBoundary::Document); } diff --git a/crates/rue-formatter/src/emit.rs b/crates/rue-formatter/src/emit.rs index af893f81..9ff4e5c7 100644 --- a/crates/rue-formatter/src/emit.rs +++ b/crates/rue-formatter/src/emit.rs @@ -52,8 +52,8 @@ impl<'a> Formatter<'a> { (None, None) if previous.compact_group.is_some() && previous.compact_group == item.compact_group - && (item.span.start.index() == 0 - || self.stream.gap_before(item.span.start).newlines <= 1) => + && (item.span.start().index() == 0 + || self.stream.gap_before(item.span.start()).newlines <= 1) => { Separator::Hard } @@ -97,26 +97,39 @@ impl<'a> Formatter<'a> { } fn span_inner(&mut self, span: TokenSpan, allow_outer_group: bool) -> Result { - if allow_outer_group - && self + let exact_group = self + .layout + .facts(span.start()) + .group_end + .is_some_and(|end| self.stream.boundary_after(end) == span.end()); + if exact_group { + if !self .layout - .facts(span.start) - .group_end - .is_some_and(|end| end.next() == span.end) - { - return self.grouped_span(span); + .facts(span.start()) + .binary_operators + .is_empty() + { + return Ok(self.binary_span(span)?.group()); + } + if allow_outer_group { + return self.grouped_span(span); + } } let mut docs = Vec::new(); - let mut index = span.start; - while index.index() < span.end.index() { + let mut index = span.start(); + while index.index() < span.end().index() { let mut emitted_separator = false; let facts = self.layout.facts(index); let atom_end = if let Some(group_end) = facts.group_end - && group_end.index() < span.end.index() - && !(index == span.start && group_end.next() == span.end) + && self.stream.boundary_after(group_end).index() < span.end().index() + && !(index == span.start() && self.stream.boundary_after(group_end) == span.end()) { - let next = group_end.next(); + let next = self.stream.next_token(group_end).ok_or_else(|| { + FormatError::Internal( + "group end has no following token inside formatting span".to_string(), + ) + })?; let gap = self.stream.gap_before(next); if !facts.binary_operators.is_empty() && self.stream.tokens.get(next.index()).is_some_and(|_| { @@ -130,18 +143,22 @@ impl<'a> Formatter<'a> { { docs.push( Doc::concat([ - self.binary_span(TokenSpan::new(index, next))?, + self.binary_span( + self.stream.span(index, self.stream.boundary_before(next))?, + )?, Doc::if_break(Doc::hard_line(), Doc::space()), ]) .group(), ); emitted_separator = true; } else { - docs.push(self.grouped_span(TokenSpan::new(index, next))?); + docs.push(self.grouped_span( + self.stream.span(index, self.stream.boundary_before(next))?, + )?); } group_end } else if let Some(close) = facts.pair { - if close.index() >= span.end.index() { + if close.index() >= span.end().index() { return Err(FormatError::Internal( "delimiter pair crosses formatting span".to_string(), )); @@ -153,36 +170,55 @@ impl<'a> Formatter<'a> { index }; - index = atom_end.next(); - if index.index() < span.end.index() && !emitted_separator { - let separator = self.separator(atom_end, index); - docs.push(self.gap_doc(self.stream.gap_before(index), separator)); + let next_boundary = self.stream.boundary_after(atom_end); + if next_boundary.index() < span.end().index() { + index = self.stream.token_at(next_boundary).ok_or_else(|| { + FormatError::Internal( + "formatting span contains a non-token boundary".to_string(), + ) + })?; + if !emitted_separator { + let separator = self.separator(atom_end, index); + docs.push(self.gap_doc(self.stream.gap_before(index), separator)); + } + } else { + break; } } Ok(Doc::concat(docs)) } fn grouped_span(&mut self, span: TokenSpan) -> Result { - if !self.layout.facts(span.start).binary_operators.is_empty() { + if !self.layout.facts(span.start()).binary_operators.is_empty() { return Ok(self.binary_span(span)?.group()); } Ok(self.span_inner(span, false)?.indent().group()) } fn binary_span(&mut self, span: TokenSpan) -> Result { - let operators = self.layout.facts(span.start).binary_operators.clone(); + let operators = self.layout.facts(span.start()).binary_operators.clone(); let Some(&first_operator) = operators.first() else { return self.span_inner(span, false); }; - let mut docs = vec![self.span_inner(TokenSpan::new(span.start, first_operator), false)?]; + let mut docs = vec![ + self.span_inner( + self.stream + .span(span.start(), self.stream.boundary_before(first_operator))?, + false, + )?, + ]; for (position, operator) in operators.iter().enumerate() { - let operand_start = operator.next(); - let segment_end = operators.get(position + 1).copied().unwrap_or(span.end); + let operand_start = self.stream.next_token(*operator).ok_or_else(|| { + FormatError::Internal("binary operator has no operand token".to_string()) + })?; + let segment_end = operators + .get(position + 1) + .map_or(span.end(), |next| self.stream.boundary_before(*next)); let operator_doc = self.token(*operator); let after_operator = self.gap_doc(self.stream.gap_before(operand_start), Separator::Space); - let operand = self.span_inner(TokenSpan::new(operand_start, segment_end), false)?; + let operand = self.span_inner(self.stream.span(operand_start, segment_end)?, false)?; let segment = Doc::concat([operator_doc, after_operator, operand]); if is_comparison_operator(self.stream.token(*operator).kind) && self.stream.gap_before(*operator).comments.is_empty() @@ -204,7 +240,9 @@ impl<'a> Formatter<'a> { let supports_trailing_comma = facts.supports_trailing_comma(); let open_doc = self.token(open); let close_doc = self.token(close); - let inner_start = open.next(); + let inner_start = self.stream.next_token(open).ok_or_else(|| { + FormatError::Internal("opening delimiter has no following token".to_string()) + })?; if inner_start == close { let gap = self.gap_doc_with_comments( @@ -226,12 +264,19 @@ impl<'a> Formatter<'a> { return self.import_group(open_doc, close_doc, &plan); } - let source_trailing_comma = supports_trailing_comma - && self.stream.token(TokenId::new(close.index() - 1)).kind == T![,]; - let inner_end = TokenId::new(close.index() - usize::from(source_trailing_comma)); - let inner = self.span(TokenSpan::new(inner_start, inner_end))?; + let previous = self.stream.previous_token(close).ok_or_else(|| { + FormatError::Internal("closing delimiter has no preceding token".to_string()) + })?; + let source_trailing_comma = + supports_trailing_comma && self.stream.token(previous).kind == T![,]; + let inner_end = if source_trailing_comma { + self.stream.boundary_before(previous) + } else { + self.stream.boundary_before(close) + }; + let inner = self.span(self.stream.span(inner_start, inner_end)?)?; let comma = if source_trailing_comma { - let leading = self.gap_doc(self.stream.gap_before(inner_end), Separator::None); + let leading = self.gap_doc(self.stream.gap(inner_end), Separator::None); self.consumed_tokens += 1; Doc::concat([leading, Doc::if_break(Doc::text(","), Doc::Nil)]) } else if supports_trailing_comma { diff --git a/crates/rue-formatter/src/equivalence.rs b/crates/rue-formatter/src/equivalence.rs index ea277cce..49b085be 100644 --- a/crates/rue-formatter/src/equivalence.rs +++ b/crates/rue-formatter/src/equivalence.rs @@ -29,7 +29,7 @@ pub(crate) fn comment_signature( .items .iter() .map(|item| { - covered_gaps.insert(item.span.start.index()); + covered_gaps.insert(item.span.start().index()); let anchor = item.identity_key.clone(); append_trivia(&mut signature, &anchor, "leading", &item.leading); append_trivia(&mut signature, &anchor, "trailing", &item.trailing); @@ -54,7 +54,7 @@ pub(crate) fn comment_signature( for item in &group.items { let anchor = format!("path:{}", item.identity_key); - covered_gaps.insert(item.span.start.index()); + covered_gaps.insert(item.span.start().index()); if let Some(comma) = item.comma { covered_gaps.insert(comma.index()); } @@ -108,7 +108,7 @@ fn append_trivia(signature: &mut Vec, anchor: &str, role: &str, trivia: } fn normalized_relative_gap(stream: &TokenStream, span: TokenSpan, gap: usize) -> usize { - (span.start.index()..gap) + (span.start().index()..gap) .filter(|index| !is_optional_trailing_comma(stream, *index)) .count() } @@ -127,7 +127,7 @@ fn is_optional_trailing_comma(stream: &TokenStream, index: usize) -> bool { } fn contains_gap(span: TokenSpan, gap: usize) -> bool { - span.start.index() < gap && gap < span.end.index() + span.start().index() < gap && gap < span.end().index() } fn find_close( @@ -141,7 +141,7 @@ fn find_close( rue_parser::T!['}'] => { depth -= 1; if depth == 0 { - return Ok(crate::token_stream::TokenId::new(index)); + return stream.token_id(index); } } _ => {} diff --git a/crates/rue-formatter/src/ordering.rs b/crates/rue-formatter/src/ordering.rs index 08cd3f2b..0505442d 100644 --- a/crates/rue-formatter/src/ordering.rs +++ b/crates/rue-formatter/src/ordering.rs @@ -6,7 +6,7 @@ use rue_parser::{SyntaxKind, SyntaxNode, T}; use crate::{ FormatError, token_stream::{TokenId, TokenSpan, TokenStream}, - trivia::{Trivia, split_between, split_file_header}, + trivia::{Trivia, split_between, split_file_header, split_group_opening}, }; #[derive(Debug, Clone)] @@ -60,7 +60,7 @@ pub(crate) fn plan_document( let is_import = matches!(&item, AstItem::ImportItem(_)); let import_group = is_import.then(|| { if !previous_was_import - || (span.start.index() > 0 && gap_has_blank_line(stream.gap_before(span.start))) + || (span.start().index() > 0 && gap_has_blank_line(stream.gap_before(span.start()))) { next_import_group += 1; } @@ -74,14 +74,14 @@ pub(crate) fn plan_document( ) .then_some(item.syntax().kind()) .filter(|_| !item.syntax().text().to_string().contains('\n')); - let path_key = stream.tokens[span.start.index()..span.end.index()] + let path_key = stream.tokens[span.start().index()..span.end().index()] .iter() .filter(|token| !matches!(token.kind, T![import] | T![export] | T![;])) .map(|token| token.text.as_str()) .collect::(); let keyword_key = stream .tokens - .get(span.start.index()) + .get(span.start().index()) .map_or("", |token| token.text.as_str()); items.push(DocumentItem { @@ -104,10 +104,10 @@ pub(crate) fn plan_document( }); } - let (header, first_leading) = split_file_header(stream.gap_before(items[0].span.start)); + let (header, first_leading) = split_file_header(stream.gap_before(items[0].span.start())); items[0].leading = first_leading; for index in 1..items.len() { - let (trailing, leading) = split_between(stream.gap_before(items[index].span.start)); + let (trailing, leading) = split_between(stream.gap_before(items[index].span.start())); items[index - 1].trailing = trailing; items[index].leading = leading; } @@ -171,11 +171,9 @@ pub(crate) fn plan_import_groups( for (original_index, path) in paths.iter().enumerate() { let span = node_span(path, stream)?; let comma = stream - .tokens - .get(span.end.index()) - .filter(|token| token.kind == T![,]) - .map(|_| span.end); - let sort_key = stream.tokens[span.start.index()..span.end.index()] + .token_at(span.end()) + .filter(|id| stream.token(*id).kind == T![,]); + let sort_key = stream.tokens[span.start().index()..span.end().index()] .iter() .map(|token| token.text.as_str()) .collect(); @@ -194,10 +192,11 @@ pub(crate) fn plan_import_groups( }); } - let (opening, first_leading) = split_between(stream.gap_before(items[0].span.start)); + let (opening, first_leading) = + split_group_opening(stream.gap_before(items[0].span.start())); items[0].leading = first_leading; for index in 1..items.len() { - let boundary = stream.gap_before(items[index].span.start); + let boundary = stream.gap_before(items[index].span.start()); let (trailing, leading) = split_between(boundary); items[index - 1].trailing = trailing; items[index].leading = leading; @@ -217,7 +216,7 @@ pub(crate) fn plan_import_groups( groups.insert( open, ImportGroupPlan { - opening: Trivia::dangling(opening.gap), + opening, items, closing: Trivia::dangling(closing.gap), }, @@ -227,15 +226,14 @@ pub(crate) fn plan_import_groups( } fn item_span(node: &SyntaxNode, stream: &TokenStream) -> Result { - let mut span = node_span(node, stream)?; - if stream - .tokens - .get(span.end.index()) - .is_some_and(|token| token.kind == T![;]) - { - span.end = span.end.next(); - } - Ok(span) + let span = node_span(node, stream)?; + let Some(semicolon) = stream + .token_at(span.end()) + .filter(|id| stream.token(*id).kind == T![;]) + else { + return Ok(span); + }; + stream.span_through(span.start(), semicolon) } fn node_span(node: &SyntaxNode, stream: &TokenStream) -> Result { @@ -245,10 +243,8 @@ fn node_span(node: &SyntaxNode, stream: &TokenStream) -> Result Result { @@ -259,7 +255,7 @@ fn find_matching_close(open: TokenId, stream: &TokenStream) -> Result { depth -= 1; if depth == 0 { - return Ok(TokenId::new(index)); + return stream.token_id(index); } } _ => {} diff --git a/crates/rue-formatter/src/tests/imports.rs b/crates/rue-formatter/src/tests/imports.rs index d1315e7a..4b891da8 100644 --- a/crates/rue-formatter/src/tests/imports.rs +++ b/crates/rue-formatter/src/tests/imports.rs @@ -85,6 +85,24 @@ fn nested_import_comments_have_stable_owners() { ); } +#[test] +fn nested_group_banners_stay_with_the_opener() { + check( + "import root::{\n// group banner\n// group banner\n\n// zeta docs\nzeta,\n// alpha docs\nalpha};", + expect![[r#" + import root::{ + // group banner + // group banner + + // alpha docs + alpha, + // zeta docs + zeta, + }; + "#]], + ); +} + #[test] fn duplicate_identical_comments_are_preserved_per_import() { check( diff --git a/crates/rue-formatter/src/token_stream.rs b/crates/rue-formatter/src/token_stream.rs index 8761261b..134d4e83 100644 --- a/crates/rue-formatter/src/token_stream.rs +++ b/crates/rue-formatter/src/token_stream.rs @@ -8,28 +8,33 @@ use crate::FormatError; pub(crate) struct TokenId(usize); impl TokenId { - pub(crate) fn new(index: usize) -> Self { - Self(index) - } - pub(crate) fn index(self) -> usize { self.0 } +} - pub(crate) fn next(self) -> Self { - Self(self.0 + 1) +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct TokenBoundary(usize); + +impl TokenBoundary { + pub(crate) fn index(self) -> usize { + self.0 } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct TokenSpan { - pub(crate) start: TokenId, - pub(crate) end: TokenId, + start: TokenId, + end: TokenBoundary, } impl TokenSpan { - pub(crate) fn new(start: TokenId, end: TokenId) -> Self { - Self { start, end } + pub(crate) fn start(self) -> TokenId { + self.start + } + + pub(crate) fn end(self) -> TokenBoundary { + self.end } } @@ -164,7 +169,7 @@ impl TokenStream { let token_by_offset = tokens .iter() .enumerate() - .map(|(index, token)| (token.start, TokenId::new(index))) + .map(|(index, token)| (token.start, TokenId(index))) .collect(); Ok(Self { @@ -176,11 +181,91 @@ impl TokenStream { } pub(crate) fn token(&self, id: TokenId) -> &Token { - &self.tokens[id.index()] + self.tokens + .get(id.index()) + .expect("TokenId was validated by this token stream") } pub(crate) fn gap_before(&self, id: TokenId) -> &Gap { - &self.gaps[id.index()] + self.gap(TokenBoundary(id.index())) + } + + pub(crate) fn gap(&self, boundary: TokenBoundary) -> &Gap { + self.gaps + .get(boundary.index()) + .expect("TokenBoundary was validated by this token stream") + } + + pub(crate) fn token_id(&self, index: usize) -> Result { + (index < self.tokens.len()) + .then_some(TokenId(index)) + .ok_or_else(|| { + FormatError::Internal(format!( + "token index {index} is out of range for {} tokens", + self.tokens.len() + )) + }) + } + + pub(crate) fn boundary(&self, index: usize) -> Result { + (index <= self.tokens.len()) + .then_some(TokenBoundary(index)) + .ok_or_else(|| { + FormatError::Internal(format!( + "token boundary {index} is out of range for {} tokens", + self.tokens.len() + )) + }) + } + + pub(crate) fn boundary_before(&self, id: TokenId) -> TokenBoundary { + TokenBoundary(id.index()) + } + + pub(crate) fn boundary_after(&self, id: TokenId) -> TokenBoundary { + TokenBoundary(id.index() + 1) + } + + pub(crate) fn token_at(&self, boundary: TokenBoundary) -> Option { + (boundary.index() < self.tokens.len()).then_some(TokenId(boundary.index())) + } + + pub(crate) fn next_token(&self, id: TokenId) -> Option { + self.token_at(self.boundary_after(id)) + } + + pub(crate) fn previous_token(&self, id: TokenId) -> Option { + id.index().checked_sub(1).map(TokenId) + } + + pub(crate) fn span( + &self, + start: TokenId, + end: TokenBoundary, + ) -> Result { + if end.index() > self.tokens.len() { + return Err(FormatError::Internal(format!( + "span end {} is out of range for {} tokens", + end.index(), + self.tokens.len() + ))); + } + if start.index() >= end.index() { + return Err(FormatError::Internal(format!( + "token span start {} must precede end {}", + start.index(), + end.index() + ))); + } + Ok(TokenSpan { start, end }) + } + + pub(crate) fn span_through( + &self, + start: TokenId, + last: TokenId, + ) -> Result { + self.span(start, self.boundary(last.index() + 1)?) } pub(crate) fn token_id_at_offset(&self, offset: usize) -> Result { @@ -201,3 +286,77 @@ fn newline_count(text: &str) -> usize { .count() + usize::from(text.ends_with('\r')) } + +#[cfg(test)] +mod tests { + use super::*; + + fn test_stream() -> TokenStream { + let tokens = ["a", "b", "c"] + .into_iter() + .enumerate() + .map(|(start, text)| Token { + kind: SyntaxKind::Ident, + text: text.to_string(), + start, + }) + .collect::>(); + TokenStream { + token_by_offset: tokens + .iter() + .enumerate() + .map(|(index, token)| (token.start, TokenId(index))) + .collect(), + gaps: vec![Gap::default(); tokens.len() + 1], + tokens, + comment_count: 0, + } + } + + #[test] + fn constructs_valid_bounded_spans() { + let stream = test_stream(); + let first = stream.token_id(0).unwrap(); + let second = stream.token_id(1).unwrap(); + let span = stream.span_through(first, second).unwrap(); + + assert_eq!(span.start(), first); + assert_eq!(span.end(), stream.boundary(2).unwrap()); + assert_eq!(stream.token(span.start()).text, "a"); + assert_eq!( + stream.token_at(span.end()), + Some(stream.token_id(2).unwrap()) + ); + } + + #[test] + fn end_boundary_is_not_a_token_id() { + let stream = test_stream(); + let end = stream.boundary(stream.tokens.len()).unwrap(); + let span = stream + .span(stream.token_id(0).unwrap(), end) + .expect("end-of-stream is a valid exclusive span boundary"); + + assert_eq!(span.end(), end); + assert_eq!(stream.token_at(end), None); + assert_eq!(stream.gap(end).comments.len(), 0); + assert!(stream.token_id(stream.tokens.len()).is_err()); + } + + #[test] + fn rejects_reversed_and_empty_spans() { + let stream = test_stream(); + let first = stream.token_id(0).unwrap(); + let last = stream.token_id(2).unwrap(); + + assert!(stream.span(last, stream.boundary_before(first)).is_err()); + assert!(stream.span(first, stream.boundary_before(first)).is_err()); + } + + #[test] + fn rejects_out_of_range_ids_and_boundaries() { + let stream = test_stream(); + assert!(stream.token_id(3).is_err()); + assert!(stream.boundary(4).is_err()); + } +} diff --git a/crates/rue-formatter/src/trivia.rs b/crates/rue-formatter/src/trivia.rs index 4be3b91e..9ee1516a 100644 --- a/crates/rue-formatter/src/trivia.rs +++ b/crates/rue-formatter/src/trivia.rs @@ -83,3 +83,38 @@ pub(crate) fn split_file_header(gap: &Gap) -> (Trivia, Trivia) { } (Trivia::new(header), Trivia::new(leading)) } + +/// Splits trivia after an import-group opener. Inline opener comments and +/// standalone banner blocks stay dangling on `{`; only the closest comment +/// block without a blank line before the first path becomes path documentation. +pub(crate) fn split_group_opening(gap: &Gap) -> (Trivia, Trivia) { + let trailing_count = gap + .comments + .iter() + .position(|comment| comment.placement != CommentPlacement::Trailing) + .unwrap_or(gap.comments.len()); + let mut attached_start = if trailing_count == gap.comments.len() || gap.newlines > 1 { + gap.comments.len() + } else { + gap.comments.len() - 1 + }; + while attached_start > trailing_count && gap.comments[attached_start].newlines_before <= 1 { + attached_start -= 1; + } + + let mut opening = Gap { + comments: gap.comments[..attached_start].to_vec(), + newlines: 0, + }; + let mut leading = Gap { + comments: gap.comments[attached_start..].to_vec(), + newlines: gap.newlines, + }; + if let Some(first) = leading.comments.first_mut() { + opening.newlines = first.newlines_before; + first.newlines_before = 0; + } else { + opening.newlines = gap.newlines; + } + (Trivia::dangling(opening), Trivia::new(leading)) +} From 5d30723bcf8998b55ac8170369688f1669319211 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 16:57:00 -0400 Subject: [PATCH 09/21] Missed stuff --- crates/rue-formatter/src/analysis.rs | 5 ++- crates/rue-formatter/src/emit.rs | 30 ++++++++-------- crates/rue-formatter/src/equivalence.rs | 33 +++++++++-------- crates/rue-formatter/src/ordering.rs | 26 ++++++-------- crates/rue-formatter/src/token_stream.rs | 45 ++++++++++++++++++++++-- 5 files changed, 87 insertions(+), 52 deletions(-) diff --git a/crates/rue-formatter/src/analysis.rs b/crates/rue-formatter/src/analysis.rs index e04b5b24..59cda8e0 100644 --- a/crates/rue-formatter/src/analysis.rs +++ b/crates/rue-formatter/src/analysis.rs @@ -81,7 +81,7 @@ pub(crate) struct Layout { impl Layout { pub(crate) fn new(document: &AstDocument, stream: &TokenStream) -> Result { - let mut facts = vec![TokenFacts::default(); stream.tokens.len()]; + let mut facts = vec![TokenFacts::default(); stream.len()]; pair_delimiters(stream, &mut facts)?; analyze_nodes(document.syntax(), stream, &mut facts)?; analyze_boundaries(document, stream, &mut facts)?; @@ -101,8 +101,7 @@ impl Layout { fn pair_delimiters(stream: &TokenStream, facts: &mut [TokenFacts]) -> Result<(), FormatError> { let mut stack: Vec<(SyntaxKind, TokenId)> = Vec::new(); - for (index, token) in stream.tokens.iter().enumerate() { - let id = stream.token_id(index)?; + for (id, token) in stream.token_ids() { match token.kind { T!['('] | T!['['] | T!['{'] => stack.push((token.kind, id)), T![')'] | T![']'] | T!['}'] => { diff --git a/crates/rue-formatter/src/emit.rs b/crates/rue-formatter/src/emit.rs index 9ff4e5c7..5b94c454 100644 --- a/crates/rue-formatter/src/emit.rs +++ b/crates/rue-formatter/src/emit.rs @@ -76,11 +76,11 @@ impl<'a> Formatter<'a> { } docs.push(self.trivia_doc(&footer, Separator::None)); - if self.consumed_tokens != self.stream.tokens.len() { + if self.consumed_tokens != self.stream.len() { return Err(FormatError::Internal(format!( "emitted {} of {} significant tokens", self.consumed_tokens, - self.stream.tokens.len() + self.stream.len() ))); } if self.consumed_comments != self.stream.comment_count { @@ -103,12 +103,7 @@ impl<'a> Formatter<'a> { .group_end .is_some_and(|end| self.stream.boundary_after(end) == span.end()); if exact_group { - if !self - .layout - .facts(span.start()) - .binary_operators - .is_empty() - { + if !self.layout.facts(span.start()).binary_operators.is_empty() { return Ok(self.binary_span(span)?.group()); } if allow_outer_group { @@ -122,7 +117,7 @@ impl<'a> Formatter<'a> { let mut emitted_separator = false; let facts = self.layout.facts(index); let atom_end = if let Some(group_end) = facts.group_end - && self.stream.boundary_after(group_end).index() < span.end().index() + && group_end.index() < span.end().index() && !(index == span.start() && self.stream.boundary_after(group_end) == span.end()) { let next = self.stream.next_token(group_end).ok_or_else(|| { @@ -132,12 +127,11 @@ impl<'a> Formatter<'a> { })?; let gap = self.stream.gap_before(next); if !facts.binary_operators.is_empty() - && self.stream.tokens.get(next.index()).is_some_and(|_| { - self.layout - .facts(next) - .delimiter_style - .is_some_and(|style| style == DelimiterStyle::Block) - }) + && self + .layout + .facts(next) + .delimiter_style + .is_some_and(|style| style == DelimiterStyle::Block) && gap.comments.is_empty() && gap.newlines <= 1 { @@ -353,7 +347,11 @@ impl<'a> Formatter<'a> { close: Doc, plan: &ImportGroupPlan, ) -> Result { - let mut docs = vec![self.trivia_doc(&plan.opening, Separator::None)]; + let mut opening = plan.opening.clone(); + if let Some(first) = opening.gap.comments.first_mut() { + first.newlines_before = 0; + } + let mut docs = vec![self.trivia_doc(&opening, Separator::None)]; for (position, item) in plan.items.iter().enumerate() { let mut leading = item.leading.clone(); if position == 0 diff --git a/crates/rue-formatter/src/equivalence.rs b/crates/rue-formatter/src/equivalence.rs index 49b085be..03806adb 100644 --- a/crates/rue-formatter/src/equivalence.rs +++ b/crates/rue-formatter/src/equivalence.rs @@ -23,7 +23,7 @@ pub(crate) fn comment_signature( append_trivia(&mut signature, "file", "header", &document_plan.header); append_trivia(&mut signature, "file", "footer", &document_plan.footer); - covered_gaps.insert(stream.tokens.len()); + covered_gaps.insert(stream.end_boundary().index()); let document_units: Vec<_> = document_plan .items @@ -65,7 +65,8 @@ pub(crate) fn comment_signature( } } - for (gap_index, gap) in stream.gaps.iter().enumerate() { + for (boundary, gap) in stream.gaps() { + let gap_index = boundary.index(); if covered_gaps.contains(&gap_index) { continue; } @@ -114,16 +115,18 @@ fn normalized_relative_gap(stream: &TokenStream, span: TokenSpan, gap: usize) -> } fn is_optional_trailing_comma(stream: &TokenStream, index: usize) -> bool { - stream - .tokens - .get(index) - .is_some_and(|token| token.kind == rue_parser::T![,]) - && stream.tokens.get(index + 1).is_some_and(|token| { - matches!( - token.kind, - rue_parser::T![')'] | rue_parser::T![']'] | rue_parser::T!['}'] | rue_parser::T![>] - ) - }) + stream.token_id(index).is_ok_and(|id| { + stream.token(id).kind == rue_parser::T![,] + && stream.next_token(id).is_some_and(|next| { + matches!( + stream.token(next).kind, + rue_parser::T![')'] + | rue_parser::T![']'] + | rue_parser::T!['}'] + | rue_parser::T![>] + ) + }) + }) } fn contains_gap(span: TokenSpan, gap: usize) -> bool { @@ -135,13 +138,13 @@ fn find_close( stream: &TokenStream, ) -> Result { let mut depth = 0; - for index in open.index()..stream.tokens.len() { - match stream.tokens[index].kind { + for (id, token) in stream.token_ids().skip(open.index()) { + match token.kind { rue_parser::T!['{'] => depth += 1, rue_parser::T!['}'] => { depth -= 1; if depth == 0 { - return stream.token_id(index); + return Ok(id); } } _ => {} diff --git a/crates/rue-formatter/src/ordering.rs b/crates/rue-formatter/src/ordering.rs index 0505442d..497f0598 100644 --- a/crates/rue-formatter/src/ordering.rs +++ b/crates/rue-formatter/src/ordering.rs @@ -74,15 +74,13 @@ pub(crate) fn plan_document( ) .then_some(item.syntax().kind()) .filter(|_| !item.syntax().text().to_string().contains('\n')); - let path_key = stream.tokens[span.start().index()..span.end().index()] + let path_key = stream + .tokens_in(span) .iter() .filter(|token| !matches!(token.kind, T![import] | T![export] | T![;])) .map(|token| token.text.as_str()) .collect::(); - let keyword_key = stream - .tokens - .get(span.start().index()) - .map_or("", |token| token.text.as_str()); + let keyword_key = stream.token(span.start()).text.as_str(); items.push(DocumentItem { span, @@ -98,7 +96,7 @@ pub(crate) fn plan_document( if items.is_empty() { return Ok(DocumentPlan { - header: Trivia::new(stream.gaps[0].clone()), + header: Trivia::new(stream.first_gap().clone()), items, footer: Trivia::default(), }); @@ -114,12 +112,7 @@ pub(crate) fn plan_document( let last = items .last_mut() .expect("non-empty document plan has a final item"); - let (trailing, footer) = split_between( - stream - .gaps - .last() - .expect("a token stream always has a final gap"), - ); + let (trailing, footer) = split_between(stream.final_gap()); last.trailing = trailing; items.sort_by( @@ -173,7 +166,8 @@ pub(crate) fn plan_import_groups( let comma = stream .token_at(span.end()) .filter(|id| stream.token(*id).kind == T![,]); - let sort_key = stream.tokens[span.start().index()..span.end().index()] + let sort_key = stream + .tokens_in(span) .iter() .map(|token| token.text.as_str()) .collect(); @@ -249,13 +243,13 @@ fn node_span(node: &SyntaxNode, stream: &TokenStream) -> Result Result { let mut depth = 0; - for index in open.index()..stream.tokens.len() { - match stream.tokens[index].kind { + for (id, token) in stream.token_ids().skip(open.index()) { + match token.kind { T!['{'] => depth += 1, T!['}'] => { depth -= 1; if depth == 0 { - return stream.token_id(index); + return Ok(id); } } _ => {} diff --git a/crates/rue-formatter/src/token_stream.rs b/crates/rue-formatter/src/token_stream.rs index 134d4e83..6b73178c 100644 --- a/crates/rue-formatter/src/token_stream.rs +++ b/crates/rue-formatter/src/token_stream.rs @@ -69,8 +69,8 @@ pub(crate) enum CommentPlacement { #[derive(Debug, Clone)] pub(crate) struct TokenStream { - pub(crate) tokens: Vec, - pub(crate) gaps: Vec, + tokens: Vec, + gaps: Vec, pub(crate) comment_count: usize, token_by_offset: HashMap, } @@ -186,6 +186,23 @@ impl TokenStream { .expect("TokenId was validated by this token stream") } + pub(crate) fn len(&self) -> usize { + self.tokens.len() + } + + pub(crate) fn token_ids(&self) -> impl Iterator { + self.tokens + .iter() + .enumerate() + .map(|(index, token)| (TokenId(index), token)) + } + + pub(crate) fn tokens_in(&self, span: TokenSpan) -> &[Token] { + self.tokens + .get(span.start().index()..span.end().index()) + .expect("TokenSpan was validated by this token stream") + } + pub(crate) fn gap_before(&self, id: TokenId) -> &Gap { self.gap(TokenBoundary(id.index())) } @@ -196,6 +213,27 @@ impl TokenStream { .expect("TokenBoundary was validated by this token stream") } + pub(crate) fn first_gap(&self) -> &Gap { + self.gap(TokenBoundary(0)) + } + + pub(crate) fn final_gap(&self) -> &Gap { + self.gaps + .last() + .expect("a token stream always has a final gap") + } + + pub(crate) fn gaps(&self) -> impl Iterator { + self.gaps + .iter() + .enumerate() + .map(|(index, gap)| (TokenBoundary(index), gap)) + } + + pub(crate) fn end_boundary(&self) -> TokenBoundary { + TokenBoundary(self.tokens.len()) + } + pub(crate) fn token_id(&self, index: usize) -> Result { (index < self.tokens.len()) .then_some(TokenId(index)) @@ -219,10 +257,12 @@ impl TokenStream { } pub(crate) fn boundary_before(&self, id: TokenId) -> TokenBoundary { + let _ = self.token(id); TokenBoundary(id.index()) } pub(crate) fn boundary_after(&self, id: TokenId) -> TokenBoundary { + let _ = self.token(id); TokenBoundary(id.index() + 1) } @@ -235,6 +275,7 @@ impl TokenStream { } pub(crate) fn previous_token(&self, id: TokenId) -> Option { + let _ = self.token(id); id.index().checked_sub(1).map(TokenId) } From 19a577f4ff83353e9c6ec8ef0ecddb8041c65a9a Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 17:02:53 -0400 Subject: [PATCH 10/21] Move validation to compile time --- Cargo.lock | 1 + crates/rue-ast/Cargo.toml | 3 + crates/rue-ast/src/lib.rs | 84 +++++++++++++++++++++++- crates/rue-formatter/src/analysis.rs | 83 +++++++++++++++++------ crates/rue-formatter/src/format.rs | 6 +- crates/rue-formatter/src/lib.rs | 7 +- crates/rue-formatter/src/syntax.rs | 66 ------------------- crates/rue-formatter/src/token_stream.rs | 2 +- 8 files changed, 155 insertions(+), 97 deletions(-) delete mode 100644 crates/rue-formatter/src/syntax.rs diff --git a/Cargo.lock b/Cargo.lock index 86066dc3..44fd082e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1714,6 +1714,7 @@ dependencies = [ name = "rue-ast" version = "0.9.0" dependencies = [ + "num-traits", "paste", "rue-parser", ] diff --git a/crates/rue-ast/Cargo.toml b/crates/rue-ast/Cargo.toml index 4f4b3360..f873c2c7 100644 --- a/crates/rue-ast/Cargo.toml +++ b/crates/rue-ast/Cargo.toml @@ -17,3 +17,6 @@ workspace = true [dependencies] rue-parser = { workspace = true } paste = { workspace = true } + +[dev-dependencies] +num-traits = { workspace = true } diff --git a/crates/rue-ast/src/lib.rs b/crates/rue-ast/src/lib.rs index 1df902a2..de1609bb 100644 --- a/crates/rue-ast/src/lib.rs +++ b/crates/rue-ast/src/lib.rs @@ -10,7 +10,47 @@ pub trait AstNode { } macro_rules! ast_nodes { - ($( $kind:ident),+ $(,)?) => { paste! { $( + ($( $kind:ident),+ $(,)?) => { paste! { + /// The exhaustive set of syntax kinds with typed AST node wrappers. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum AstNodeKind { + $( $kind, )+ + } + + impl AstNodeKind { + pub const ALL: &'static [Self] = &[ + $( Self::$kind, )+ + ]; + + pub fn from_syntax(kind: SyntaxKind) -> Option { + match kind { + $( SyntaxKind::$kind => Some(Self::$kind), )+ + _ => None, + } + } + + pub fn of(node: &SyntaxNode) -> Option { + Self::from_syntax(node.kind()) + } + } + + impl TryFrom for AstNodeKind { + type Error = SyntaxKind; + + fn try_from(kind: SyntaxKind) -> Result { + Self::from_syntax(kind).ok_or(kind) + } + } + + impl From for SyntaxKind { + fn from(kind: AstNodeKind) -> Self { + match kind { + $( AstNodeKind::$kind => Self::$kind, )+ + } + } + } + + $( #[derive(Debug, Clone)] pub struct [< Ast $kind >](SyntaxNode); @@ -26,7 +66,8 @@ macro_rules! ast_nodes { &self.0 } } - )+ } }; + )+ + } }; } macro_rules! ast_enum { @@ -900,3 +941,42 @@ impl AstStructFieldBinding { self.syntax().children().find_map(AstBinding::cast) } } + +#[cfg(test)] +mod tests { + use num_traits::FromPrimitive; + + use super::*; + + #[test] + fn every_parser_node_kind_has_an_ast_kind() { + let first = SyntaxKind::Document as u16; + let end = SyntaxKind::Error as u16; + assert_eq!(AstNodeKind::ALL.len(), usize::from(end - first)); + + for discriminant in first..end { + let syntax = SyntaxKind::from_u16(discriminant) + .expect("each contiguous parser node discriminant is valid"); + let ast = AstNodeKind::try_from(syntax) + .unwrap_or_else(|kind| panic!("missing AST node kind for {kind:?}")); + assert_eq!(SyntaxKind::from(ast), syntax); + } + } + + #[test] + fn tokens_trivia_and_sentinels_are_not_ast_node_kinds() { + for kind in [ + SyntaxKind::Whitespace, + SyntaxKind::LineComment, + SyntaxKind::Ident, + SyntaxKind::Import, + SyntaxKind::OpenBrace, + SyntaxKind::Plus, + SyntaxKind::Error, + SyntaxKind::Eof, + ] { + assert_eq!(AstNodeKind::from_syntax(kind), None); + assert_eq!(AstNodeKind::try_from(kind), Err(kind)); + } + } +} diff --git a/crates/rue-formatter/src/analysis.rs b/crates/rue-formatter/src/analysis.rs index 59cda8e0..147f7142 100644 --- a/crates/rue-formatter/src/analysis.rs +++ b/crates/rue-formatter/src/analysis.rs @@ -1,4 +1,4 @@ -use rue_ast::{AstDocument, AstNode}; +use rue_ast::{AstDocument, AstNode, AstNodeKind}; use rue_parser::{SyntaxKind, SyntaxNode, T}; use crate::{ @@ -134,28 +134,31 @@ fn analyze_nodes( facts: &mut [TokenFacts], ) -> Result<(), FormatError> { for node in root.descendants() { - match node.kind() { - SyntaxKind::Block | SyntaxKind::ModuleItem | SyntaxKind::StructItem => { + let kind = ast_node_kind(&node)?; + // Intentionally exhaustive: adding a typed AST node requires an + // explicit formatter analysis policy before this crate can compile. + match kind { + AstNodeKind::Block | AstNodeKind::ModuleItem | AstNodeKind::StructItem => { if let Some(id) = direct_or_descendant_token(&node, T!['{'], stream)? { facts[id.index()].delimiter_style = Some(DelimiterStyle::Block); - if node.kind() == SyntaxKind::StructItem { + if kind == AstNodeKind::StructItem { facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); } } } - SyntaxKind::StructInitializerExpr | SyntaxKind::StructBinding => { + AstNodeKind::StructInitializerExpr | AstNodeKind::StructBinding => { if let Some(id) = direct_or_descendant_token(&node, T!['{'], stream)? { facts[id.index()].delimiter_style = Some(DelimiterStyle::Braced); facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); } } - SyntaxKind::PairExpr - | SyntaxKind::PairType - | SyntaxKind::PairBinding - | SyntaxKind::ListExpr - | SyntaxKind::ListType - | SyntaxKind::ListBinding - | SyntaxKind::ImportPathSegment => { + AstNodeKind::PairExpr + | AstNodeKind::PairType + | AstNodeKind::PairBinding + | AstNodeKind::ListExpr + | AstNodeKind::ListType + | AstNodeKind::ListBinding + | AstNodeKind::ImportPathSegment => { if let Some(token) = node .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) @@ -165,7 +168,7 @@ fn analyze_nodes( facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); } } - SyntaxKind::GenericParameters | SyntaxKind::GenericArguments => { + AstNodeKind::GenericParameters | AstNodeKind::GenericArguments => { let direct_tokens: Vec<_> = node .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) @@ -187,7 +190,7 @@ fn analyze_nodes( facts[open.index()].pair = Some(close); } } - SyntaxKind::PrefixExpr => { + AstNodeKind::PrefixExpr => { if let Some(token) = significant_tokens(&node) .find(|token| SyntaxKind::PREFIX_OPS.contains(&token.kind())) { @@ -195,10 +198,10 @@ fn analyze_nodes( facts[id.index()].flags.insert(TokenFlags::PREFIX_OPERATOR); } } - SyntaxKind::FunctionItem - | SyntaxKind::FunctionCallExpr - | SyntaxKind::LambdaExpr - | SyntaxKind::LambdaType => { + AstNodeKind::FunctionItem + | AstNodeKind::FunctionCallExpr + | AstNodeKind::LambdaExpr + | AstNodeKind::LambdaType => { if let Some(token) = node .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) @@ -209,7 +212,7 @@ fn analyze_nodes( facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); } } - SyntaxKind::PathExpr | SyntaxKind::PathType | SyntaxKind::ImportPath => { + AstNodeKind::PathExpr | AstNodeKind::PathType | AstNodeKind::ImportPath => { if let Some(token) = significant_tokens(&node).next() && token.kind() == T![::] { @@ -219,7 +222,38 @@ fn analyze_nodes( .insert(TokenFlags::ABSOLUTE_PATH_START); } } - _ => {} + AstNodeKind::Document + | AstNodeKind::FunctionParameter + | AstNodeKind::ConstantItem + | AstNodeKind::TypeAliasItem + | AstNodeKind::StructField + | AstNodeKind::ImportItem + | AstNodeKind::LiteralType + | AstNodeKind::UnionType + | AstNodeKind::GroupType + | AstNodeKind::ListTypeItem + | AstNodeKind::LambdaParameter + | AstNodeKind::LetStmt + | AstNodeKind::ExprStmt + | AstNodeKind::IfStmt + | AstNodeKind::ReturnStmt + | AstNodeKind::AssertStmt + | AstNodeKind::RaiseStmt + | AstNodeKind::DebugStmt + | AstNodeKind::PathSegment + | AstNodeKind::StructInitializerField + | AstNodeKind::LiteralExpr + | AstNodeKind::ConstExpr + | AstNodeKind::GroupExpr + | AstNodeKind::ListItem + | AstNodeKind::BinaryExpr + | AstNodeKind::IfExpr + | AstNodeKind::GuardExpr + | AstNodeKind::CastExpr + | AstNodeKind::FieldAccessExpr + | AstNodeKind::NamedBinding + | AstNodeKind::ListBindingItem + | AstNodeKind::StructFieldBinding => {} } } Ok(()) @@ -323,6 +357,15 @@ fn token_id(token: &rue_parser::SyntaxToken, stream: &TokenStream) -> Result Result { + AstNodeKind::of(node).ok_or_else(|| { + FormatError::Internal(format!( + "syntax node {:?} has no typed AST node kind", + node.kind() + )) + }) +} + fn collect_binary_operator_offsets(node: &SyntaxNode, operators: &mut Vec) { for element in node.children_with_tokens() { match element { diff --git a/crates/rue-formatter/src/format.rs b/crates/rue-formatter/src/format.rs index c34e8283..6e1fb02c 100644 --- a/crates/rue-formatter/src/format.rs +++ b/crates/rue-formatter/src/format.rs @@ -1,15 +1,13 @@ -use rue_ast::{AstDocument, AstNode}; +use rue_ast::AstDocument; use crate::{ - FormatError, analysis::Layout, document::Doc, emit::Formatter, syntax::validate_node_kinds, - token_stream::TokenStream, + FormatError, analysis::Layout, document::Doc, emit::Formatter, token_stream::TokenStream, }; pub(crate) fn format_document( document: &AstDocument, stream: &TokenStream, ) -> Result { - validate_node_kinds(document.syntax())?; let layout = Layout::new(document, stream)?; Formatter::new(stream, layout).format() } diff --git a/crates/rue-formatter/src/lib.rs b/crates/rue-formatter/src/lib.rs index b989b6ce..b9d26972 100644 --- a/crates/rue-formatter/src/lib.rs +++ b/crates/rue-formatter/src/lib.rs @@ -15,7 +15,6 @@ mod equivalence; mod format; mod ordering; mod renderer; -mod syntax; mod token_stream; mod trivia; @@ -59,9 +58,9 @@ pub enum FormatError { /// Original parser diagnostics. diagnostics: Vec, }, - /// The syntax tree contains a kind unknown to this formatter version. - #[error("unsupported syntax kind: {0}")] - UnsupportedSyntax(SyntaxKind), + /// The lossless tree contains an unsupported trivia token. + #[error("unsupported trivia kind: {0}")] + UnsupportedTrivia(SyntaxKind), /// Lossless CST tokens were not encountered in source order. #[error("token order violation: token at {next_start} follows end offset {previous_end}")] TokenOrder { diff --git a/crates/rue-formatter/src/syntax.rs b/crates/rue-formatter/src/syntax.rs deleted file mode 100644 index 85726950..00000000 --- a/crates/rue-formatter/src/syntax.rs +++ /dev/null @@ -1,66 +0,0 @@ -use rue_parser::{SyntaxKind, SyntaxNode}; - -use crate::FormatError; - -pub(crate) fn validate_node_kinds(root: &SyntaxNode) -> Result<(), FormatError> { - for node in root.descendants() { - match node.kind() { - SyntaxKind::Document - | SyntaxKind::ModuleItem - | SyntaxKind::FunctionItem - | SyntaxKind::FunctionParameter - | SyntaxKind::ConstantItem - | SyntaxKind::TypeAliasItem - | SyntaxKind::StructItem - | SyntaxKind::StructField - | SyntaxKind::ImportItem - | SyntaxKind::ImportPath - | SyntaxKind::ImportPathSegment - | SyntaxKind::GenericParameters - | SyntaxKind::GenericArguments - | SyntaxKind::LiteralType - | SyntaxKind::PathType - | SyntaxKind::UnionType - | SyntaxKind::GroupType - | SyntaxKind::PairType - | SyntaxKind::ListType - | SyntaxKind::ListTypeItem - | SyntaxKind::LambdaType - | SyntaxKind::LambdaParameter - | SyntaxKind::Block - | SyntaxKind::LetStmt - | SyntaxKind::ExprStmt - | SyntaxKind::IfStmt - | SyntaxKind::ReturnStmt - | SyntaxKind::AssertStmt - | SyntaxKind::RaiseStmt - | SyntaxKind::DebugStmt - | SyntaxKind::PathExpr - | SyntaxKind::PathSegment - | SyntaxKind::StructInitializerExpr - | SyntaxKind::StructInitializerField - | SyntaxKind::LiteralExpr - | SyntaxKind::ConstExpr - | SyntaxKind::GroupExpr - | SyntaxKind::PairExpr - | SyntaxKind::ListExpr - | SyntaxKind::ListItem - | SyntaxKind::PrefixExpr - | SyntaxKind::BinaryExpr - | SyntaxKind::FunctionCallExpr - | SyntaxKind::IfExpr - | SyntaxKind::GuardExpr - | SyntaxKind::CastExpr - | SyntaxKind::FieldAccessExpr - | SyntaxKind::LambdaExpr - | SyntaxKind::NamedBinding - | SyntaxKind::PairBinding - | SyntaxKind::ListBinding - | SyntaxKind::ListBindingItem - | SyntaxKind::StructBinding - | SyntaxKind::StructFieldBinding => {} - kind => return Err(FormatError::UnsupportedSyntax(kind)), - } - } - Ok(()) -} diff --git a/crates/rue-formatter/src/token_stream.rs b/crates/rue-formatter/src/token_stream.rs index 6b73178c..b59117d6 100644 --- a/crates/rue-formatter/src/token_stream.rs +++ b/crates/rue-formatter/src/token_stream.rs @@ -139,7 +139,7 @@ impl TokenStream { } } kind if kind.is_trivia() => { - return Err(FormatError::UnsupportedSyntax(kind)); + return Err(FormatError::UnsupportedTrivia(kind)); } kind => { gaps.last_mut() From 56e1e58570a05cdb2967992ce0e45353a2f22288 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 17:24:23 -0400 Subject: [PATCH 11/21] Fixes --- crates/rue-formatter/src/analysis.rs | 138 +++++++- crates/rue-formatter/src/document.rs | 27 +- crates/rue-formatter/src/emit.rs | 53 ++- crates/rue-formatter/src/renderer.rs | 55 +-- crates/rue-formatter/src/tests.rs | 337 ++++++++++++++++++- examples/fizz_buzz.rue | 36 +- examples/puzzles/partial_offer.rue | 22 +- examples/puzzles/partial_offer.yaml | 2 +- examples/puzzles/singleton.rue | 12 +- tests/builtins.rue | 14 +- tests/builtins.yaml | 12 +- tests/expressions/blocks.rue | 16 +- tests/expressions/const_expr.rue | 39 +-- tests/expressions/const_expr_errors.rue | 8 +- tests/expressions/const_expr_errors.yaml | 4 +- tests/expressions/const_expr_unresolved.rue | 4 +- tests/expressions/const_expr_unresolved.yaml | 2 +- tests/external/signature_types.rue | 4 +- tests/std.rue | 11 +- tests/std.yaml | 2 +- 20 files changed, 645 insertions(+), 153 deletions(-) diff --git a/crates/rue-formatter/src/analysis.rs b/crates/rue-formatter/src/analysis.rs index 147f7142..48f075ff 100644 --- a/crates/rue-formatter/src/analysis.rs +++ b/crates/rue-formatter/src/analysis.rs @@ -1,4 +1,4 @@ -use rue_ast::{AstDocument, AstNode, AstNodeKind}; +use rue_ast::{AstDocument, AstExpr, AstNode, AstNodeKind}; use rue_parser::{SyntaxKind, SyntaxNode, T}; use crate::{ @@ -12,6 +12,16 @@ pub(crate) enum DelimiterStyle { Block, Braced, Group, + Fill, + FillBraced, + Hug, + Vertical, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SingleArgumentLayout { + Hug, + Vertical, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -138,7 +148,15 @@ fn analyze_nodes( // Intentionally exhaustive: adding a typed AST node requires an // explicit formatter analysis policy before this crate can compile. match kind { - AstNodeKind::Block | AstNodeKind::ModuleItem | AstNodeKind::StructItem => { + AstNodeKind::Block => { + if let Some(id) = direct_or_descendant_token(&node, T!['{'], stream)? { + facts[id.index()].delimiter_style = Some( + expression_block_style(&node, id, stream, facts) + .unwrap_or(DelimiterStyle::Block), + ); + } + } + AstNodeKind::ModuleItem | AstNodeKind::StructItem => { if let Some(id) = direct_or_descendant_token(&node, T!['{'], stream)? { facts[id.index()].delimiter_style = Some(DelimiterStyle::Block); if kind == AstNodeKind::StructItem { @@ -166,6 +184,22 @@ fn analyze_nodes( { let id = token_id(&token, stream)?; facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); + if matches!(kind, AstNodeKind::PairExpr | AstNodeKind::ListExpr) + && sole_list_item_expression_kind(&node) + .is_some_and(is_transparent_expression_wrapper) + && !delimiter_contains_comments(id, stream, facts) + { + facts[id.index()].delimiter_style = Some(DelimiterStyle::Fill); + } + } + } + AstNodeKind::GroupExpr => { + if let Some(id) = direct_or_descendant_token(&node, T!['('], stream)? + && sole_direct_expression_kind(&node) + .is_some_and(is_transparent_expression_wrapper) + && !delimiter_contains_comments(id, stream, facts) + { + facts[id.index()].delimiter_style = Some(DelimiterStyle::Fill); } } AstNodeKind::GenericParameters | AstNodeKind::GenericArguments => { @@ -210,6 +244,19 @@ fn analyze_nodes( let id = token_id(&token, stream)?; facts[id.index()].flags.insert(TokenFlags::ATTACHED_OPENER); facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); + if kind == AstNodeKind::FunctionCallExpr + && let Some(argument_layout) = single_argument_layout(&node) + { + facts[id.index()].delimiter_style = Some( + if argument_layout == SingleArgumentLayout::Hug + && !delimiter_contains_comments(id, stream, facts) + { + DelimiterStyle::Hug + } else { + DelimiterStyle::Vertical + }, + ); + } } } AstNodeKind::PathExpr | AstNodeKind::PathType | AstNodeKind::ImportPath => { @@ -244,7 +291,6 @@ fn analyze_nodes( | AstNodeKind::StructInitializerField | AstNodeKind::LiteralExpr | AstNodeKind::ConstExpr - | AstNodeKind::GroupExpr | AstNodeKind::ListItem | AstNodeKind::BinaryExpr | AstNodeKind::IfExpr @@ -259,6 +305,92 @@ fn analyze_nodes( Ok(()) } +fn single_argument_layout(call: &SyntaxNode) -> Option { + let kind = sole_list_item_expression_kind(call)?; + Some(if is_transparent_expression_wrapper(kind) { + SingleArgumentLayout::Hug + } else { + SingleArgumentLayout::Vertical + }) +} + +fn sole_list_item_expression_kind(node: &SyntaxNode) -> Option { + let mut items = node + .children() + .filter(|child| AstNodeKind::of(child) == Some(AstNodeKind::ListItem)); + let item = items.next()?; + if items.next().is_some() { + return None; + } + sole_direct_expression_kind(&item) +} + +fn sole_direct_expression_kind(node: &SyntaxNode) -> Option { + let mut expressions = node.children().filter_map(AstExpr::cast); + let expression = expressions.next()?; + if expressions.next().is_some() { + return None; + } + AstNodeKind::of(expression.syntax()) +} + +fn is_transparent_expression_wrapper(kind: AstNodeKind) -> bool { + matches!( + kind, + AstNodeKind::FunctionCallExpr + | AstNodeKind::StructInitializerExpr + | AstNodeKind::ConstExpr + | AstNodeKind::GroupExpr + | AstNodeKind::PairExpr + | AstNodeKind::ListExpr + | AstNodeKind::Block + ) +} + +fn expression_block_style( + node: &SyntaxNode, + open: TokenId, + stream: &TokenStream, + facts: &[TokenFacts], +) -> Option { + if delimiter_contains_comments(open, stream, facts) + || node.parent().is_some_and(|parent| { + matches!( + parent.kind(), + SyntaxKind::FunctionItem + | SyntaxKind::ModuleItem + | SyntaxKind::IfExpr + | SyntaxKind::IfStmt + ) + }) + { + return None; + } + + let mut children = node.children(); + let expression = children.next().and_then(AstExpr::cast)?; + if children.next().is_some() { + return None; + } + let kind = AstNodeKind::of(expression.syntax())?; + Some(if is_transparent_expression_wrapper(kind) { + DelimiterStyle::FillBraced + } else { + DelimiterStyle::Braced + }) +} + +fn delimiter_contains_comments(open: TokenId, stream: &TokenStream, facts: &[TokenFacts]) -> bool { + let Some(close) = facts[open.index()].pair else { + return false; + }; + ((open.index() + 1)..=close.index()).any(|index| { + stream + .boundary(index) + .is_ok_and(|boundary| !stream.gap(boundary).comments.is_empty()) + }) +} + fn analyze_boundaries( document: &AstDocument, stream: &TokenStream, diff --git a/crates/rue-formatter/src/document.rs b/crates/rue-formatter/src/document.rs index ea32a545..633bbe6a 100644 --- a/crates/rue-formatter/src/document.rs +++ b/crates/rue-formatter/src/document.rs @@ -7,12 +7,15 @@ pub(crate) enum Doc { Concat(Vec), Line(LineKind), Indent(Box), + Outdent(Box), Group(Box), /// A local fill boundary with continuation indentation applied only when /// that boundary breaks. Ordinary groups cannot express that conditional /// indentation when their surrounding binary chain is already broken. Fill { - doc: Box, + flat: Box, + broken: Box, + space_when_flat: bool, indent_on_break: bool, }, IfBreak { @@ -55,7 +58,23 @@ impl Doc { pub(crate) fn fill(doc: Self, indent_on_break: bool) -> Self { Self::Fill { - doc: Box::new(doc), + flat: Box::new(doc.clone()), + broken: Box::new(doc), + space_when_flat: true, + indent_on_break, + } + } + + pub(crate) fn fill_choice( + flat: Self, + broken: Self, + space_when_flat: bool, + indent_on_break: bool, + ) -> Self { + Self::Fill { + flat: Box::new(flat), + broken: Box::new(broken), + space_when_flat, indent_on_break, } } @@ -72,6 +91,10 @@ impl Doc { Self::Indent(Box::new(self)) } + pub(crate) fn outdent(self) -> Self { + Self::Outdent(Box::new(self)) + } + pub(crate) fn group(self) -> Self { Self::Group(Box::new(self)) } diff --git a/crates/rue-formatter/src/emit.rs b/crates/rue-formatter/src/emit.rs index 5b94c454..7a85d142 100644 --- a/crates/rue-formatter/src/emit.rs +++ b/crates/rue-formatter/src/emit.rs @@ -263,6 +263,7 @@ impl<'a> Formatter<'a> { })?; let source_trailing_comma = supports_trailing_comma && self.stream.token(previous).kind == T![,]; + let suppress_broken_comma = style == DelimiterStyle::Hug; let inner_end = if source_trailing_comma { self.stream.boundary_before(previous) } else { @@ -272,13 +273,19 @@ impl<'a> Formatter<'a> { let comma = if source_trailing_comma { let leading = self.gap_doc(self.stream.gap(inner_end), Separator::None); self.consumed_tokens += 1; - Doc::concat([leading, Doc::if_break(Doc::text(","), Doc::Nil)]) - } else if supports_trailing_comma { + Doc::concat([ + leading, + if suppress_broken_comma { + Doc::Nil + } else { + Doc::if_break(Doc::text(","), Doc::Nil) + }, + ]) + } else if supports_trailing_comma && !suppress_broken_comma { Doc::if_break(Doc::text(","), Doc::Nil) } else { Doc::Nil }; - let inner = Doc::concat([inner, comma]); let leading_gap = self.stream.gap_before(inner_start); let trailing_gap = self.stream.gap_before(close); let leading_comment_starts_line = leading_gap @@ -295,7 +302,11 @@ impl<'a> Formatter<'a> { match style { DelimiterStyle::Block => Separator::Hard, DelimiterStyle::Braced => Separator::Soft, - DelimiterStyle::Group => Separator::None, + DelimiterStyle::Group + | DelimiterStyle::Fill + | DelimiterStyle::FillBraced + | DelimiterStyle::Hug + | DelimiterStyle::Vertical => Separator::None, }, true, false, @@ -305,19 +316,46 @@ impl<'a> Formatter<'a> { match style { DelimiterStyle::Block => Separator::Hard, DelimiterStyle::Braced => Separator::Soft, - DelimiterStyle::Group => Separator::None, + DelimiterStyle::Group + | DelimiterStyle::Fill + | DelimiterStyle::FillBraced + | DelimiterStyle::Hug + | DelimiterStyle::Vertical => Separator::None, }, ); Ok(match style { DelimiterStyle::Block | DelimiterStyle::Braced => Doc::concat([ open_doc, - Doc::concat([leading, inner]).indent(), + Doc::concat([leading, inner, comma]).indent(), trailing, close_doc, ]) .group_if(style == DelimiterStyle::Braced), - DelimiterStyle::Group => Doc::concat([ + DelimiterStyle::Fill | DelimiterStyle::FillBraced | DelimiterStyle::Hug => { + let space_inside = style == DelimiterStyle::FillBraced; + let closing_space = if space_inside { Doc::space() } else { Doc::Nil }; + let flat_inner = Doc::concat([ + leading.clone(), + inner.clone(), + trailing.clone(), + closing_space, + close_doc.clone(), + ]); + let broken_inner = Doc::concat([ + leading, + inner, + comma, + trailing, + Doc::concat([Doc::hard_line(), close_doc]).outdent(), + ]); + Doc::concat([ + open_doc, + Doc::fill_choice(flat_inner, broken_inner, space_inside, true), + ]) + .group() + } + DelimiterStyle::Group | DelimiterStyle::Vertical => Doc::concat([ open_doc, Doc::concat([ if leading_comment_starts_line { @@ -327,6 +365,7 @@ impl<'a> Formatter<'a> { }, leading, inner, + comma, ]) .indent(), trailing, diff --git a/crates/rue-formatter/src/renderer.rs b/crates/rue-formatter/src/renderer.rs index fc97d306..f4bc1253 100644 --- a/crates/rue-formatter/src/renderer.rs +++ b/crates/rue-formatter/src/renderer.rs @@ -46,24 +46,29 @@ pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { column += 1; } Doc::Fill { - doc, + flat, + broken, + space_when_flat, indent_on_break, } => { - if column < options.max_width + let separator_width = usize::from(*space_when_flat); + if column + separator_width <= options.max_width && fits( - options.max_width - column - 1, + options.max_width - column - separator_width, command.indent, - doc, + flat, &commands, Mode::Broken, ) { - output.push(' '); - column += 1; + if *space_when_flat { + output.push(' '); + column += 1; + } commands.push(Command { indent: command.indent, mode: command.mode, - doc, + doc: flat, }); } else { output.push('\n'); @@ -74,7 +79,7 @@ pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { commands.push(Command { indent, mode: command.mode, - doc, + doc: broken, }); } } @@ -91,6 +96,11 @@ pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { mode: command.mode, doc, }), + Doc::Outdent(doc) => commands.push(Command { + indent: command.indent.saturating_sub(options.indent_width), + mode: command.mode, + doc, + }), Doc::Group(doc) => { let mode = if has_forced_line(doc) || !fits( @@ -130,8 +140,8 @@ fn has_forced_line(doc: &Doc) -> bool { Doc::Nil | Doc::Text(_) | Doc::Line(LineKind::Soft) => false, Doc::Line(LineKind::Hard | LineKind::Empty) => true, Doc::Concat(docs) => docs.iter().any(has_forced_line), - Doc::Indent(doc) | Doc::Group(doc) => has_forced_line(doc), - Doc::Fill { doc, .. } => has_forced_line(doc), + Doc::Indent(doc) | Doc::Outdent(doc) | Doc::Group(doc) => has_forced_line(doc), + Doc::Fill { flat, .. } => has_forced_line(flat), Doc::IfBreak { flat, .. } => has_forced_line(flat), } } @@ -175,24 +185,31 @@ fn fits( } remaining -= 1; } - Doc::Fill { doc, .. } => { - if remaining == 0 { + Doc::Fill { + flat, + space_when_flat, + .. + } => { + let separator_width = usize::from(*space_when_flat); + if separator_width > remaining { return false; } - remaining -= 1; + remaining -= separator_width; commands.push(Command { indent: command.indent, mode: command.mode, - doc, + doc: flat, }); } Doc::Line(LineKind::Soft) => return true, Doc::Line(LineKind::Hard | LineKind::Empty) => return true, - Doc::Indent(doc) | Doc::Group(doc) => commands.push(Command { - indent: command.indent, - mode: command.mode, - doc, - }), + Doc::Indent(doc) | Doc::Outdent(doc) | Doc::Group(doc) => { + commands.push(Command { + indent: command.indent, + mode: command.mode, + doc, + }); + } Doc::IfBreak { broken, flat } => commands.push(Command { indent: command.indent, mode: command.mode, diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index d098cbda..b50b7e2f 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -98,13 +98,9 @@ fn all_expression_families() { debug fn(a: T): T => a; assert x is Int; return if x > 0 { - { - x - }.field as Int + { x }.field as Int } else { - const { - x - } + const { x } }; } "#]], @@ -152,9 +148,10 @@ fn nested_delimiters_use_single_indent() { .unwrap(); expect![[r#" fn main() { - assert tree_hash( - fizz_buzz(1, 15), - ) == tree_hash([1, 2, 3, 4, 5, 6]); + assert tree_hash(fizz_buzz( + 1, + 15, + )) == tree_hash([1, 2, 3, 4, 5, 6]); } "#]] .assert_eq(&output); @@ -473,14 +470,336 @@ fn collection_and_struct_expressions() { let value = 1; value }; + const { 1 } + } + "#]], + ); +} + +#[test] +fn expression_blocks_stay_compact_when_their_expression_fits() { + check( + "fn main(){const{1};{2}}", + expect![[r#" + fn main() { + const { 1 }; + { 2 } + } + "#]], + ); +} + +#[test] +fn expression_blocks_are_vertical_around_wrapped_binary_chains() { + let options = FormatOptions { + max_width: 32, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){const{first_long_value+second_long_value+third_long_value}}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + const { + first_long_value + + second_long_value + + third_long_value + } + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn standalone_expression_blocks_are_vertical_around_binary_chains() { + let options = FormatOptions { + max_width: 32, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){{first_long_value+second_long_value+third_long_value}}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + { + first_long_value + + second_long_value + + third_long_value + } + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn binary_expression_blocks_obey_the_full_width_boundary() { + let exact = FormatOptions { + max_width: 28, + ..FormatOptions::default() + }; + let flat = format_source("fn main(){const{first+second}}", &exact).unwrap(); + expect![[r#" + fn main() { + const { first + second } + } + "#]] + .assert_eq(&flat); + + let narrow = FormatOptions { + max_width: 27, + ..FormatOptions::default() + }; + let vertical = format_source("fn main(){const{first+second}}", &narrow).unwrap(); + expect![[r#" + fn main() { + const { + first + second + } + } + "#]] + .assert_eq(&vertical); + assert_eq!(format_source(&vertical, &narrow).unwrap(), vertical); +} + +#[test] +fn flat_calls_remain_flat() { + check( + "fn main(){func(a,b)}", + expect![[r#" + fn main() { + func(a, b) + } + "#]], + ); +} + +#[test] +fn binary_call_arguments_use_vertical_single_expression_layout() { + let options = FormatOptions { + max_width: 32, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){consume(first_long_value+second_long_value+third_long_value,)}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + consume( + first_long_value + + second_long_value + + third_long_value, + ) + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn one_argument_outer_calls_fill_before_broken_inner_lists() { + let options = FormatOptions { + max_width: 30, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){outer(inner(first_long_value,second_long_value),)}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + outer(inner( + first_long_value, + second_long_value, + )) + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn one_argument_call_hugging_recurses_through_wrappers() { + let options = FormatOptions { + max_width: 30, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){func({const{(first_operand,second_operand)}})}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + func({ const { ( + first_operand, + second_operand, + ) } }) + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn binary_inside_hugged_call_wrapper_breaks_at_inner_call() { + let options = FormatOptions { + max_width: 32, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){outer(inner(first_long_value+second_long_value+third_long_value),)}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + outer(inner( + first_long_value + + second_long_value + + third_long_value, + )) + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn single_argument_comments_force_vertical_layout_with_comma() { + let options = FormatOptions { + max_width: 32, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){outer(/* note */ first_long_value+second_long_value)}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + outer( + /* note */ first_long_value + + second_long_value, + ) + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn call_hugging_obeys_exact_width_boundary() { + let exact = FormatOptions { + max_width: 25, + ..FormatOptions::default() + }; + let flat = format_source("fn main(){outer(inner(a,b,c))}", &exact).unwrap(); + expect![[r#" + fn main() { + outer(inner(a, b, c)) + } + "#]] + .assert_eq(&flat); + + let narrow = FormatOptions { + max_width: 24, + ..FormatOptions::default() + }; + let broken = format_source("fn main(){outer(inner(a,b,c))}", &narrow).unwrap(); + expect![[r#" + fn main() { + outer(inner( + a, + b, + c, + )) + } + "#]] + .assert_eq(&broken); + assert_eq!(format_source(&broken, &narrow).unwrap(), broken); +} + +#[test] +fn multiple_arguments_keep_fully_broken_list_layout() { + let options = FormatOptions { + max_width: 24, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){outer(first_long_value,second_long_value)}", + &options, + ) + .unwrap(); + expect![[r#" + fn main() { + outer( + first_long_value, + second_long_value, + ) + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn const_blocks_with_comments_or_statements_remain_blocks() { + check( + "fn main(){const{// explain\n1};const{let value=1;value}}", + expect![[r#" + fn main() { const { + // explain 1 + }; + const { + let value = 1; + value } } "#]], ); } +#[test] +fn fill_layout_obeys_exact_width_and_custom_indentation() { + let exact = FormatOptions { + max_width: 17, + indent_width: 2, + }; + let flat = format_source("fn main(){const{12345}}", &exact).unwrap(); + expect![[r#" + fn main() { + const { 12345 } + } + "#]] + .assert_eq(&flat); + + let narrow = FormatOptions { + max_width: 14, + indent_width: 2, + }; + let broken = format_source("fn main(){const{12345}}", &narrow).unwrap(); + expect![[r#" + fn main() { + const { + 12345 + } + } + "#]] + .assert_eq(&broken); + assert_eq!(format_source(&broken, &narrow).unwrap(), broken); +} + #[test] fn calls_generics_spreads_and_postfix_expressions() { check( diff --git a/examples/fizz_buzz.rue b/examples/fizz_buzz.rue index bb60ecc2..a3dacc24 100644 --- a/examples/fizz_buzz.rue +++ b/examples/fizz_buzz.rue @@ -40,23 +40,21 @@ fn single_digit_to_string(digit: Int) -> String { test fn tests() { assert tree_hash(fizz_buzz(1, 1)) == tree_hash(["1"]); assert tree_hash(fizz_buzz(1, 5)) == tree_hash(["1", "2", "Fizz", "4", "Buzz"]); - assert tree_hash(fizz_buzz(1, 15)) == tree_hash( - [ - "1", - "2", - "Fizz", - "4", - "Buzz", - "Fizz", - "7", - "8", - "Fizz", - "Buzz", - "11", - "Fizz", - "13", - "14", - "FizzBuzz", - ], - ); + assert tree_hash(fizz_buzz(1, 15)) == tree_hash([ + "1", + "2", + "Fizz", + "4", + "Buzz", + "Fizz", + "7", + "8", + "Fizz", + "Buzz", + "11", + "Fizz", + "13", + "14", + "FizzBuzz", + ]); } diff --git a/examples/puzzles/partial_offer.rue b/examples/puzzles/partial_offer.rue index 3798251d..0a1cb37b 100644 --- a/examples/puzzles/partial_offer.rue +++ b/examples/puzzles/partial_offer.rue @@ -34,20 +34,18 @@ fn main( let assert_puzzle_announcement = AssertPuzzleAnnouncement { id: sha256( other_asset_offer_mod - + tree_hash( + + tree_hash([ + proof.parent_coin_id, [ - proof.parent_coin_id, - [ - receiver_puzzle_hash, - if other_asset_amount > min_other_asset_amount_minus_one { - other_asset_amount - } else { - raise "Other asset amount is too small"; - }, - [receiver_puzzle_hash], - ], + receiver_puzzle_hash, + if other_asset_amount > min_other_asset_amount_minus_one { + other_asset_amount + } else { + raise "Other asset amount is too small"; + }, + [receiver_puzzle_hash], ], - ), + ]), ), }; diff --git a/examples/puzzles/partial_offer.yaml b/examples/puzzles/partial_offer.yaml index aadcafe8..c2281a75 100644 --- a/examples/puzzles/partial_offer.yaml +++ b/examples/puzzles/partial_offer.yaml @@ -1,5 +1,5 @@ program: (a (q 2 (q 4 (c (q . 63) (c (sha256 23 (a 5 (c 5 (c 1279 (c (c 47 (c (a (i (> 1535 -65) (q . 1535) (q 8)) 1) (c (c 47 ()) ()))) ()))))) ())) (c (c (q . 70) (c (coinid 1279 (a 11 (c 2815 6143)) 5887) ())) (c (i (> 4 ()) (c (q . 51) (c 2815 (c 4 (c (c 2815 ()) ())))) (q 1)) (i (a (i 3071 (q 2 (i (> 11263 (q . -1)) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 6) 6)))) (c (c (- 2943 (/ (* 767 319) 447)) (a 47 4095)) 1)) (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) 1)) -debug_program: (a (q 2 (q 2 (q 4 45 (c 61 (c 4 6))) (c (c (i (> 4 ()) (c (q . 51) (c (f (r 767)) (c 4 (c (c (f (r 767)) ()) ())))) (c (q . 1) ())) (i (a (i (not (not (l 3071))) (q 2 (i (> (f (r 3071)) (- () (q . 1))) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 10) 10)) 1)) (c (c (- (f (r (r 383))) (/ (* 767 (f -65)) (r -65))) (c (a 47 4095) (c (c (q . 63) (c (sha256 (concat 11 (a 2 (c 2 (c (f 383) (c (c 23 (c (a (i (> 767 95) (q . 767) (q 8 (q . "raise called at partial_offer.rue:45:33") (q . "Other asset amount is too small"))) 1) (c (c 23 ()) ()))) ())))))) ())) (c (q . 70) (c (coinid (f 383) (a 5 (c (f (r 383)) 3071)) (f (r (r 383)))) ()))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) 1)) +debug_program: (a (q 2 (q 2 (q 4 45 (c 61 (c 4 6))) (c (c (i (> 4 ()) (c (q . 51) (c (f (r 767)) (c 4 (c (c (f (r 767)) ()) ())))) (c (q . 1) ())) (i (a (i (not (not (l 3071))) (q 2 (i (> (f (r 3071)) (- () (q . 1))) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 10) 10)) 1)) (c (c (- (f (r (r 383))) (/ (* 767 (f -65)) (r -65))) (c (a 47 4095) (c (c (q . 63) (c (sha256 (concat 11 (a 2 (c 2 (c (f 383) (c (c 23 (c (a (i (> 767 95) (q . 767) (q 8 (q . "raise called at partial_offer.rue:44:29") (q . "Other asset amount is too small"))) 1) (c (c 23 ()) ()))) ())))))) ())) (c (q . 70) (c (coinid (f 383) (a 5 (c (f (r 383)) 3071)) (f (r (r 383)))) ()))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) 1)) solution: () byte_cost: 5100000 clvm_error: path into atom diff --git a/examples/puzzles/singleton.rue b/examples/puzzles/singleton.rue index abfef1ab..1210996f 100644 --- a/examples/puzzles/singleton.rue +++ b/examples/puzzles/singleton.rue @@ -122,11 +122,9 @@ test fn tests() { [CreateCoin { puzzle_hash: p2_puzzle_hash, amount: 1 }], ); - assert tree_hash(conditions) == tree_hash( - [ - AssertMyAmount { amount: 1 }, - AssertMyParentId { parent_id: launcher_id }, - CreateCoin { puzzle_hash: singleton_puzzle_hash(singleton, p2_puzzle_hash), amount: 1 }, - ], - ); + assert tree_hash(conditions) == tree_hash([ + AssertMyAmount { amount: 1 }, + AssertMyParentId { parent_id: launcher_id }, + CreateCoin { puzzle_hash: singleton_puzzle_hash(singleton, p2_puzzle_hash), amount: 1 }, + ]); } diff --git a/tests/builtins.rue b/tests/builtins.rue index 14a7328a..1ae7b66a 100644 --- a/tests/builtins.rue +++ b/tests/builtins.rue @@ -12,17 +12,19 @@ test fn casting() { test fn hash_functions() { assert sha256("hello") == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; - assert sha256( - ...["hel", "lo"], - ) == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; + assert sha256(...[ + "hel", + "lo", + ]) == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; assert sha256_inline( "hello", ) == 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824; assert keccak256("hello") == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; - assert keccak256( - ...["hel", "lo"], - ) == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; + assert keccak256(...[ + "hel", + "lo", + ]) == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; assert keccak256_inline( "hello", ) == 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8; diff --git a/tests/builtins.yaml b/tests/builtins.yaml index f9882f8b..6b4f1459 100644 --- a/tests/builtins.yaml +++ b/tests/builtins.yaml @@ -8,42 +8,42 @@ tests: total_cost: 2270017 - name: hash_functions program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (q "hel" 27759)) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (q "hel" 27759)) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 9 21 29)) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8)) 1) (q 8)) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) - debug_program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 (concat (concat 9 21) 29))) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8 (q . "assertion failed at builtins.rue:36:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:35:5"))) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8 (q . "assertion failed at builtins.rue:26:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:23:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:22:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:18:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:15:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:14:5"))) 1) + debug_program: (a (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (a (c (c (q . 11) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (sha256 (q . "hello")) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (a (c (c (q . 62) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (i (= (keccak256 (q . "hello")) (q . 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8)) (q 2 (q 2 (q 2 (i (= 2 (sha256 (concat (concat 9 21) 29))) (q 2 (i (= 2 (q . 0xb74ea4f137ada0db15f35bb38f3ae75e5a92d0c9bb2dc8745ed8b202d8397add)) (q) (q 8 (q . "assertion failed at builtins.rue:38:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:37:5"))) 1) (c (coinid 4 10 14) 1)) (c (c (sha256 (q . "parent_coin_id")) (c (sha256 (q . "puzzle_hash")) (q . 1))) 1)) (q 8 (q . "assertion failed at builtins.rue:28:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:24:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:23:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:19:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:15:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:14:5"))) 1) output: () runtime_cost: 10577 byte_cost: 8328000 total_cost: 8338577 - name: byte_manipulation program: (a (i (a (c (c (q . 14) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 14) ()) (q "hello")) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (q "hel" 27759)) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 14) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hello") ())) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8 (q . "assertion failed at builtins.rue:46:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:45:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:44:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:42:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:41:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:40:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 14) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hello") ())) ()) (q . "hello")) (q 2 (i (= (a (c (c (q . 14) ()) (c (q . "hel") (c (q . 27759) ()))) ()) (q . "hello")) (q 2 (i (= (substr (q . "hello") (q . 2)) (q . "llo")) (q 2 (i (= (substr (q . "hello") () (q . 2)) (q . 26725)) (q 2 (i (= (substr (q . "hello") () (q . 5)) (q . "hello")) (q) (q 8 (q . "assertion failed at builtins.rue:48:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:47:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:46:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:44:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:43:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:42:5"))) 1) output: () runtime_cost: 4460 byte_cost: 3720000 total_cost: 3724460 - name: arithmetic program: (a (i (a (c (c (q . 16) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 16) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (q 1 2)) ()) (q . 3)) (q 2 (i (a (c (c (q . 17) ()) ()) ()) (q 8) (q 2 (i (= (a (c (c (q . 17) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (q 1 2)) ()) (q . -1)) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (q 2 3)) ()) (q . 6)) (q 2 (q 2 (i (= 4 (q . 3)) (q 2 (i (= 6 (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 3 . 1) 1)) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1) (q 8)) 1) (q 8)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 16) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) (c (q . 2) ()))) ()) (q . 3)) (q 2 (i (= (a (c (c (q . 17) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) (c (q . 2) ()))) ()) (- () (q . 1))) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 2) (c (q . 3) ()))) ()) (q . 6)) (q 2 (q 2 (i (= (f 2) (q . 3)) (q 2 (i (= (r 2) (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8 (q . "assertion failed at builtins.rue:66:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:64:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:63:5"))) 1) (c (divmod (q . 10) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:60:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:59:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:58:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:56:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:55:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:54:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:52:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:51:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:50:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 16) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 16) ()) (c (q . 1) (c (q . 2) ()))) ()) (q . 3)) (q 2 (i (= (a (c (c (q . 17) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 17) ()) (c (q . 1) (c (q . 2) ()))) ()) (- () (q . 1))) (q 2 (i (= (a (c (c (q . 18) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 18) ()) (c (q . 2) (c (q . 3) ()))) ()) (q . 6)) (q 2 (q 2 (i (= (f 2) (q . 3)) (q 2 (i (= (r 2) (q . 1)) (q 2 (i (= (modpow (q . 2) (q . 3) (q . 5)) (q . 3)) (q) (q 8 (q . "assertion failed at builtins.rue:68:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:66:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:65:5"))) 1) (c (divmod (q . 10) (q . 3)) 1)) (q 8 (q . "assertion failed at builtins.rue:62:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:61:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:60:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:58:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:57:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:56:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:54:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:53:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:52:5"))) 1) output: () runtime_cost: 28961 byte_cost: 6768000 total_cost: 6796961 - name: boolean_logic program: (a (i (a (c (c (q . 33) ()) ()) ()) (q 8) (q 2 (i (a (c (c (q . 33) ()) (q ())) ()) (q 8) (q 2 (i (= (a (c (c (q . 33) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (q () 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (a (c (c (q . 34) ()) (q ())) ()) (q 8) (q 2 (i (= (a (c (c (q . 34) ()) (q 1)) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (q 1 1)) ()) (q . 1)) (q) (q 8)) 1) (q 8)) 1)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) 1)) 1) - debug_program: (a (i (= (a (c (c (q . 33) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (c () (c (q . 1) ()))) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) (c (q . 1) ()))) ()) (q . 1)) (q) (q 8 (q . "assertion failed at builtins.rue:78:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:77:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:76:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:75:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:73:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:72:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:71:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:70:5"))) 1) + debug_program: (a (i (= (a (c (c (q . 33) ()) ()) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 33) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 33) ()) (c () (c (q . 1) ()))) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) ()) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c () ())) ()) ()) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) ())) ()) (q . 1)) (q 2 (i (= (a (c (c (q . 34) ()) (c (q . 1) (c (q . 1) ()))) ()) (q . 1)) (q) (q 8 (q . "assertion failed at builtins.rue:80:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:79:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:78:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:77:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:75:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:74:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:73:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:72:5"))) 1) output: () runtime_cost: 9752 byte_cost: 4668000 total_cost: 4677752 - name: g1_math program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5) 1)) (c (point_add) ())) - debug_program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:103:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:102:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:101:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:100:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:99:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:98:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:97:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:95:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:94:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:93:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:92:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:91:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:89:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:86:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:85:5"))) 1) (c (c (q . 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b) (q . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5)) 1)) (c (point_add) ())) + debug_program: (a (q 2 (q 2 (i (= (pubkey_for_exp ()) 5) (q 2 (i (= (pubkey_for_exp (q . 0x0000000000000000000000000000000000000000000000000000000000000000)) 5) (q 2 (i (= (pubkey_for_exp (sha256 ())) 4) (q 2 (i (= (a (c (c (q . 29) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 29) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 49) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 49) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:105:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:104:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:103:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:102:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:101:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:100:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:99:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:97:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:96:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:95:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:94:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:93:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:91:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:88:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:87:5"))) 1) (c (c (q . 0x8646e8b10532e235ddb8fd7edcdb8d8b76c1006af0518e39d6cc2a84d059d1858f7749e4af3571eb8660d0d3aa91954b) (q . 0xa895cfd6482f4ff7328e0aa65a7902654bfaaf473eadf53a30620d04500949b1b2003c4559cd3a4bed3125f9ea2825d5)) 1)) (c (point_add) ())) output: () runtime_cost: 28164410 byte_cost: 10956000 total_cost: 39120410 - name: g2_math program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1) (c (q 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063 . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a) 1)) (c (g2_add) ())) - debug_program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:122:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:121:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:120:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:119:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:118:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:117:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:116:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:114:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:113:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:112:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:111:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:110:5"))) 1) (c (c (q . 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063) (q . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a)) 1)) (c (g2_add) ())) + debug_program: (a (q 2 (q 2 (i (= (a (c (c (q . 52) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 52) ()) (c 4 (c 4 ()))) ()) 6) (q 2 (i (= (a (c (c (q . 53) ()) ()) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 ())) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 5 (c 5 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 ())) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 4 (c 4 ()))) ()) 5) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 ()))) ()) 4) (q 2 (i (= (a (c (c (q . 53) ()) (c 6 (c 4 (c 4 ())))) ()) 5) (q) (q 8 (q . "assertion failed at builtins.rue:124:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:123:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:122:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:121:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:120:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:119:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:118:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:116:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:115:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:114:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:113:5"))) 1) (q 8 (q . "assertion failed at builtins.rue:112:5"))) 1) (c (c (q . 0xb1e87e3c3edbc445c23ec72974abf75ddf37d2958ee46e7c1e625feb3038a22915d5625b09b988d45ac125b43b8bb3bc16de0110df42517cf5e832584015b8af417eaa77df04acf45b4ef5ce0e574d3f5c1a3fc41cbea00500e2da5699ba9063) (q . 0x8842468e1b71c650a1c89917df83e9d3c7be9768c326cfb3acd392ab9e7bc03631ee0fe5597b6bd64a7d056713c19f6e0269fb581460167adcc15c3602b9d84bdfa3089568697512a2bc09d08be3db872a461f87ced501ccc600830b2ae4df9a)) 1)) (c (g2_add) ())) output: () runtime_cost: 34216780 byte_cost: 10596000 diff --git a/tests/expressions/blocks.rue b/tests/expressions/blocks.rue index eb10c98b..f52ddc6a 100644 --- a/tests/expressions/blocks.rue +++ b/tests/expressions/blocks.rue @@ -1,14 +1,10 @@ test fn simple() -> Int { - { - 42 - } + { 42 } } test fn capture() -> Int { let value = 42; - { - value * 2 - } + { value * 2 } } test fn shadow() -> Int { @@ -31,11 +27,5 @@ test fn binding() -> Int { } test fn nested() -> Int { - { - { - { - 42 - } - } - } + { { { 42 } } } } diff --git a/tests/expressions/const_expr.rue b/tests/expressions/const_expr.rue index ebde5d4d..6d739233 100644 --- a/tests/expressions/const_expr.rue +++ b/tests/expressions/const_expr.rue @@ -1,15 +1,11 @@ const BASE: Int = 5; test fn forward_const_function() -> Int { - const { - later_const() - } + const { later_const() } } fn later_const() -> Int { - const { - 42 - } + const { 42 } } fn double(value: Int) -> Int { @@ -25,9 +21,7 @@ fn factorial(value: Int) -> Int { } test fn literal() -> Int { - const { - 1 + 2 - } + const { 1 + 2 } } test fn block() -> Int { @@ -38,42 +32,27 @@ test fn block() -> Int { } test fn pair() -> (Int, Int) { - const { - (1, 2) - } + const { (1, 2) } } test fn list() -> List { - const { - [1, 2, 3] - } + const { [1, 2, 3] } } test fn const_item() -> Int { - const { - BASE * 2 - } + const { BASE * 2 } } test fn function() -> Int { - const { - double(BASE) - } + const { double(BASE) } } test fn recursive_function() -> Int { - const { - factorial(5) - } + const { factorial(5) } } test fn nested() -> Int { - const { - const { - 1 + 2 - } - + 3 - } + const { const { 1 + 2 } + 3 } } test fn debug_effect() -> Int { diff --git a/tests/expressions/const_expr_errors.rue b/tests/expressions/const_expr_errors.rue index 5a11493e..02343cca 100644 --- a/tests/expressions/const_expr_errors.rue +++ b/tests/expressions/const_expr_errors.rue @@ -1,14 +1,10 @@ test fn parameter(value: Int) -> Int { - const { - value + 1 - } + const { value + 1 } } test fn outer_binding() -> Int { let value = 1; - const { - value + 1 - } + const { value + 1 } } test fn raises() -> Int { diff --git a/tests/expressions/const_expr_errors.yaml b/tests/expressions/const_expr_errors.yaml index 4f1979c9..60166d4a 100644 --- a/tests/expressions/const_expr_errors.yaml +++ b/tests/expressions/const_expr_errors.yaml @@ -1,4 +1,4 @@ diagnostics: - Const expression depends on runtime value `value` at const_expr_errors.rue:2:5 -- Const expression depends on runtime value `value` at const_expr_errors.rue:9:5 -- 'Const expression failed to evaluate: clvm raise at const_expr_errors.rue:15:5' +- Const expression depends on runtime value `value` at const_expr_errors.rue:7:5 +- 'Const expression failed to evaluate: clvm raise at const_expr_errors.rue:11:5' diff --git a/tests/expressions/const_expr_unresolved.rue b/tests/expressions/const_expr_unresolved.rue index 413e36fd..15df821d 100644 --- a/tests/expressions/const_expr_unresolved.rue +++ b/tests/expressions/const_expr_unresolved.rue @@ -1,5 +1,3 @@ test fn unresolved() -> Int { - const { - missing - } + const { missing } } diff --git a/tests/expressions/const_expr_unresolved.yaml b/tests/expressions/const_expr_unresolved.yaml index 73a1d9b5..ab273357 100644 --- a/tests/expressions/const_expr_unresolved.yaml +++ b/tests/expressions/const_expr_unresolved.yaml @@ -1,2 +1,2 @@ diagnostics: -- Undeclared symbol `missing` at const_expr_unresolved.rue:3:9 +- Undeclared symbol `missing` at const_expr_unresolved.rue:2:13 diff --git a/tests/external/signature_types.rue b/tests/external/signature_types.rue index 95157a8c..58fb06fe 100644 --- a/tests/external/signature_types.rue +++ b/tests/external/signature_types.rue @@ -11,7 +11,5 @@ struct SecondProof { type Proof = FirstProof | SecondProof; fn main() -> Bytes32 { - const { - tree_hash(external) - } + const { tree_hash(external) } } diff --git a/tests/std.rue b/tests/std.rue index ed7deee9..8a27ed2f 100644 --- a/tests/std.rue +++ b/tests/std.rue @@ -64,9 +64,14 @@ test fn currying() { } test fn recursion() { - check_each( - [nil, "Hello, world!", (100, 200), [1, 2, 3], fn(a: Int, b: Int) => a + b, check_each], - ); + check_each([ + nil, + "Hello, world!", + (100, 200), + [1, 2, 3], + fn(a: Int, b: Int) => a + b, + check_each, + ]); } fn check_each(list: List) { diff --git a/tests/std.yaml b/tests/std.yaml index 4d7ad6fa..2af2f4fa 100644 --- a/tests/std.yaml +++ b/tests/std.yaml @@ -22,7 +22,7 @@ tests: total_cost: 4831127 - name: recursion program: (a (q 3 () (a 10 (c 10 (c 4 (c 14 (c (c () (c (q . "Hello, world!") (c (q 100 . 200) (c (q 1 2 3) (c (q 16 2 5) (c (c (q . 2) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 14) (c (q . 1) ()))) ()))) ()))) ()))) ())))))) ()))))) ()) (c (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (c (q 2 (i 23 (q 2 (q 2 (i (a 23 (c 23 (c 4 4))) (q 2 (i (= (a 11 (c 11 4)) (a 11 (c 11 4))) (q 3 () (a 5 (c 5 (c 11 (c 23 (c 6 ()))))) ()) (q 8)) 1) (q 8)) 1) (c 23 1)) (q)) 1) (q 2 (i (l 7) (q 2 (i (l 5) (q 2 (i (a 2 (c 2 (c 9 11))) (q 2 (i (a 2 (c 2 (c 13 15))) (q 1 . 1) (q)) 1) (q)) 1) (q)) 1) (q 2 (i (l 5) (q) (q 9 7 5)) 1)) 1))) ())) - debug_program: (a (q 3 (q . 1) () (a 10 (c 10 (c 4 (c 14 (c (c () (c (q . "Hello, world!") (c (c (q . 100) (q . 200)) (c (c (q . 1) (c (q . 2) (c (q . 3) ()))) (c (c (q . 2) (c (c (q . 1) (q 16 2 5)) (c (q . 1) ()))) (c (c (q . 2) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 14) (c (q . 1) ()))) ()))) ()))) ()))) ())))))) ())))))) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (c (q 2 (i (not (l 23)) (q) (q 2 (q 2 (i (a 23 (c 23 (c (f 2) (f 2)))) (q 2 (i (= (a 11 (c 11 (f 2))) (a 11 (c 11 (f 2)))) (q 3 (q . 1) () (a 5 (c 5 (c 11 (c 23 (c (r 2) ())))))) (q 8 (q . "assertion failed at std.rue:80:5"))) 1) (q 8 (q . "assertion failed at std.rue:79:5"))) 1) (c 23 1))) 1) (q 2 (i (not (l 7)) (q 2 (i (not (l 5)) (q 9 7 5) (q)) 1) (q 2 (i (not (l 5)) (q) (q 2 (i (a 2 (c 2 (c (f 5) (f 7)))) (q 2 (i (a 2 (c 2 (c (r 5) (r 7)))) (q 1 . 1) (q)) 1) (q)) 1)) 1)) 1))) ())) + debug_program: (a (q 3 (q . 1) () (a 10 (c 10 (c 4 (c 14 (c (c () (c (q . "Hello, world!") (c (c (q . 100) (q . 200)) (c (c (q . 1) (c (q . 2) (c (q . 3) ()))) (c (c (q . 2) (c (c (q . 1) (q 16 2 5)) (c (q . 1) ()))) (c (c (q . 2) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 10) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 14) (c (q . 1) ()))) ()))) ()))) ()))) ())))))) ())))))) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (c (q 2 (i (not (l 23)) (q) (q 2 (q 2 (i (a 23 (c 23 (c (f 2) (f 2)))) (q 2 (i (= (a 11 (c 11 (f 2))) (a 11 (c 11 (f 2)))) (q 3 (q . 1) () (a 5 (c 5 (c 11 (c 23 (c (r 2) ())))))) (q 8 (q . "assertion failed at std.rue:85:5"))) 1) (q 8 (q . "assertion failed at std.rue:84:5"))) 1) (c 23 1))) 1) (q 2 (i (not (l 7)) (q 2 (i (not (l 5)) (q 9 7 5) (q)) 1) (q 2 (i (not (l 5)) (q) (q 2 (i (a 2 (c 2 (c (f 5) (f 7)))) (q 2 (i (a 2 (c 2 (c (r 5) (r 7)))) (q 1 . 1) (q)) 1) (q)) 1)) 1)) 1))) ())) output: () runtime_cost: 2402920 byte_cost: 7512000 From 0dd9b5b9c164df31604a5d46860126804b74c0e4 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 17:32:56 -0400 Subject: [PATCH 12/21] Format with operator precedence taken into account --- crates/rue-formatter/src/analysis.rs | 68 +++++++++++--- crates/rue-formatter/src/document.rs | 10 +-- crates/rue-formatter/src/emit.rs | 67 +++++++++----- crates/rue-formatter/src/renderer.rs | 9 +- crates/rue-formatter/src/tests.rs | 123 ++++++++++++++++++++++++++ crates/rue-parser/src/grammar/expr.rs | 16 +--- crates/rue-parser/src/syntax_kind.rs | 28 ++++++ examples/puzzles/partial_offer.rue | 4 +- 8 files changed, 267 insertions(+), 58 deletions(-) diff --git a/crates/rue-formatter/src/analysis.rs b/crates/rue-formatter/src/analysis.rs index 48f075ff..5ff2be2d 100644 --- a/crates/rue-formatter/src/analysis.rs +++ b/crates/rue-formatter/src/analysis.rs @@ -31,13 +31,19 @@ pub(crate) enum ItemBoundary { Block, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct ContinuationOperator { + pub(crate) token: TokenId, + pub(crate) depth: usize, +} + #[derive(Debug, Clone, Default)] pub(crate) struct TokenFacts { pub(crate) pair: Option, pub(crate) delimiter_style: Option, pub(crate) item_boundary: Option, pub(crate) group_end: Option, - pub(crate) binary_operators: Vec, + pub(crate) continuation_operators: Vec, flags: TokenFlags, } @@ -460,15 +466,20 @@ fn analyze_expression_groups( .map_or(end, |current| current.max(end)), ); - let mut operator_offsets = Vec::new(); + let mut operators = Vec::new(); if node.kind() == SyntaxKind::BinaryExpr { - collect_binary_operator_offsets(&node, &mut operator_offsets); + collect_binary_operators(&node, None, 0, &mut operators)?; } else { - collect_union_operator_offsets(&node, &mut operator_offsets); + collect_union_operators(&node, &mut operators); } - facts[start.index()].binary_operators = operator_offsets + facts[start.index()].continuation_operators = operators .into_iter() - .map(|offset| stream.token_id_at_offset(offset)) + .map(|operator| { + Ok(ContinuationOperator { + token: stream.token_id_at_offset(operator.offset)?, + depth: operator.depth, + }) + }) .collect::, _>>()?; } Ok(()) @@ -498,28 +509,61 @@ fn ast_node_kind(node: &SyntaxNode) -> Result { }) } -fn collect_binary_operator_offsets(node: &SyntaxNode, operators: &mut Vec) { +#[derive(Debug, Clone, Copy)] +struct OperatorOffset { + offset: usize, + depth: usize, +} + +fn collect_binary_operators( + node: &SyntaxNode, + parent_precedence: Option, + parent_depth: usize, + operators: &mut Vec, +) -> Result<(), FormatError> { + let operator = node + .children_with_tokens() + .filter_map(rowan::NodeOrToken::into_token) + .find(|token| SyntaxKind::BINARY_OPS.contains(&token.kind())) + .ok_or_else(|| { + FormatError::Internal("binary expression has no direct operator".to_string()) + })?; + let precedence = operator + .kind() + .binary_binding_power() + .ok_or_else(|| FormatError::Internal("binary operator has no precedence".to_string()))? + .0; + let depth = + parent_precedence.map_or(0, |parent| parent_depth + usize::from(precedence != parent)); + for element in node.children_with_tokens() { match element { rowan::NodeOrToken::Node(child) if child.kind() == SyntaxKind::BinaryExpr => { - collect_binary_operator_offsets(&child, operators); + collect_binary_operators(&child, Some(precedence), depth, operators)?; } rowan::NodeOrToken::Token(token) if SyntaxKind::BINARY_OPS.contains(&token.kind()) => { - operators.push(usize::from(token.text_range().start())); + operators.push(OperatorOffset { + offset: usize::from(token.text_range().start()), + depth, + }); } _ => {} } } + Ok(()) } -fn collect_union_operator_offsets(node: &SyntaxNode, operators: &mut Vec) { +fn collect_union_operators(node: &SyntaxNode, operators: &mut Vec) { for element in node.children_with_tokens() { match element { rowan::NodeOrToken::Node(child) if child.kind() == SyntaxKind::UnionType => { - collect_union_operator_offsets(&child, operators); + collect_union_operators(&child, operators); } rowan::NodeOrToken::Token(token) if token.kind() == T![|] => { - operators.push(usize::from(token.text_range().start())); + operators.push(OperatorOffset { + offset: usize::from(token.text_range().start()), + depth: 0, + }); } _ => {} } diff --git a/crates/rue-formatter/src/document.rs b/crates/rue-formatter/src/document.rs index 633bbe6a..a703c57f 100644 --- a/crates/rue-formatter/src/document.rs +++ b/crates/rue-formatter/src/document.rs @@ -16,7 +16,7 @@ pub(crate) enum Doc { flat: Box, broken: Box, space_when_flat: bool, - indent_on_break: bool, + indent_levels_on_break: usize, }, IfBreak { broken: Box, @@ -56,12 +56,12 @@ impl Doc { Self::Line(LineKind::Soft) } - pub(crate) fn fill(doc: Self, indent_on_break: bool) -> Self { + pub(crate) fn fill(doc: Self, indent_levels_on_break: usize) -> Self { Self::Fill { flat: Box::new(doc.clone()), broken: Box::new(doc), space_when_flat: true, - indent_on_break, + indent_levels_on_break, } } @@ -69,13 +69,13 @@ impl Doc { flat: Self, broken: Self, space_when_flat: bool, - indent_on_break: bool, + indent_levels_on_break: usize, ) -> Self { Self::Fill { flat: Box::new(flat), broken: Box::new(broken), space_when_flat, - indent_on_break, + indent_levels_on_break, } } diff --git a/crates/rue-formatter/src/emit.rs b/crates/rue-formatter/src/emit.rs index 7a85d142..be002a8e 100644 --- a/crates/rue-formatter/src/emit.rs +++ b/crates/rue-formatter/src/emit.rs @@ -103,7 +103,12 @@ impl<'a> Formatter<'a> { .group_end .is_some_and(|end| self.stream.boundary_after(end) == span.end()); if exact_group { - if !self.layout.facts(span.start()).binary_operators.is_empty() { + if !self + .layout + .facts(span.start()) + .continuation_operators + .is_empty() + { return Ok(self.binary_span(span)?.group()); } if allow_outer_group { @@ -126,7 +131,7 @@ impl<'a> Formatter<'a> { ) })?; let gap = self.stream.gap_before(next); - if !facts.binary_operators.is_empty() + if !facts.continuation_operators.is_empty() && self .layout .facts(next) @@ -183,46 +188,59 @@ impl<'a> Formatter<'a> { } fn grouped_span(&mut self, span: TokenSpan) -> Result { - if !self.layout.facts(span.start()).binary_operators.is_empty() { + if !self + .layout + .facts(span.start()) + .continuation_operators + .is_empty() + { return Ok(self.binary_span(span)?.group()); } Ok(self.span_inner(span, false)?.indent().group()) } fn binary_span(&mut self, span: TokenSpan) -> Result { - let operators = self.layout.facts(span.start()).binary_operators.clone(); - let Some(&first_operator) = operators.first() else { + let operators = self + .layout + .facts(span.start()) + .continuation_operators + .clone(); + let Some(first_operator) = operators.first() else { return self.span_inner(span, false); }; - let mut docs = vec![ - self.span_inner( - self.stream - .span(span.start(), self.stream.boundary_before(first_operator))?, - false, + let mut docs = vec![self.span_inner( + self.stream.span( + span.start(), + self.stream.boundary_before(first_operator.token), )?, - ]; + false, + )?]; for (position, operator) in operators.iter().enumerate() { - let operand_start = self.stream.next_token(*operator).ok_or_else(|| { + let operand_start = self.stream.next_token(operator.token).ok_or_else(|| { FormatError::Internal("binary operator has no operand token".to_string()) })?; let segment_end = operators .get(position + 1) - .map_or(span.end(), |next| self.stream.boundary_before(*next)); - let operator_doc = self.token(*operator); + .map_or(span.end(), |next| self.stream.boundary_before(next.token)); + let operator_doc = self.token(operator.token); let after_operator = self.gap_doc(self.stream.gap_before(operand_start), Separator::Space); let operand = self.span_inner(self.stream.span(operand_start, segment_end)?, false)?; let segment = Doc::concat([operator_doc, after_operator, operand]); - if is_comparison_operator(self.stream.token(*operator).kind) - && self.stream.gap_before(*operator).comments.is_empty() + if is_comparison_operator(self.stream.token(operator.token).kind) + && self.stream.gap_before(operator.token).comments.is_empty() { - let fill = Doc::fill(segment, position == 0); - docs.push(if position == 0 { fill } else { fill.indent() }); + let fills_at_root = position == 0 && operator.depth == 0; + let fill = Doc::fill(segment, if fills_at_root { 1 } else { operator.depth }); + docs.push(if fills_at_root { fill } else { fill.indent() }); } else { let before_operator = - self.gap_doc(self.stream.gap_before(*operator), Separator::Soft); - docs.push(Doc::concat([before_operator, segment]).indent()); + self.gap_doc(self.stream.gap_before(operator.token), Separator::Soft); + docs.push(indent_levels( + Doc::concat([before_operator, segment]), + operator.depth + 1, + )); } } Ok(Doc::concat(docs)) @@ -351,7 +369,7 @@ impl<'a> Formatter<'a> { ]); Doc::concat([ open_doc, - Doc::fill_choice(flat_inner, broken_inner, space_inside, true), + Doc::fill_choice(flat_inner, broken_inner, space_inside, 1), ]) .group() } @@ -574,6 +592,13 @@ impl GroupIf for Doc { } } +fn indent_levels(mut doc: Doc, levels: usize) -> Doc { + for _ in 0..levels { + doc = doc.indent(); + } + doc +} + fn separator_doc(separator: Separator) -> Doc { match separator { Separator::None => Doc::Nil, diff --git a/crates/rue-formatter/src/renderer.rs b/crates/rue-formatter/src/renderer.rs index f4bc1253..ca4f1794 100644 --- a/crates/rue-formatter/src/renderer.rs +++ b/crates/rue-formatter/src/renderer.rs @@ -49,7 +49,7 @@ pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { flat, broken, space_when_flat, - indent_on_break, + indent_levels_on_break, } => { let separator_width = usize::from(*space_when_flat); if column + separator_width <= options.max_width @@ -72,8 +72,7 @@ pub(crate) fn render(doc: &Doc, options: &FormatOptions) -> String { }); } else { output.push('\n'); - let indent = - command.indent + usize::from(*indent_on_break) * options.indent_width; + let indent = command.indent + indent_levels_on_break * options.indent_width; output.extend(std::iter::repeat_n(' ', indent)); column = indent; commands.push(Command { @@ -247,7 +246,7 @@ mod tests { fn fill_line_stays_flat_before_broken_suffix_when_prefix_fits() { let doc = Doc::concat([ Doc::text("lhs"), - Doc::fill(Doc::text("== rhs"), false), + Doc::fill(Doc::text("== rhs"), 0), Doc::hard_line(), Doc::text("tail"), ]); @@ -267,7 +266,7 @@ mod tests { fn fill_line_breaks_when_broken_prefix_does_not_fit() { let doc = Doc::concat([ Doc::text("long_lhs"), - Doc::fill(Doc::text("== rhs"), true), + Doc::fill(Doc::text("== rhs"), 1), Doc::hard_line(), Doc::text("tail"), ]); diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index b50b7e2f..1d0ebe0d 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -177,6 +177,129 @@ fn binary_chain_uses_one_continuation_indent() { assert_eq!(format_source(&output, &options).unwrap(), output); } +#[test] +fn binary_precedence_adds_nested_continuation_indent() { + let options = FormatOptions { + max_width: 30, + ..FormatOptions::default() + }; + let output = format_source("fn main(){first+another*third/fourth-first}", &options).unwrap(); + expect![[r#" + fn main() { + first + + another + * third + / fourth + - first + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn higher_precedence_operands_indent_on_both_sides() { + let options = FormatOptions { + max_width: 18, + ..FormatOptions::default() + }; + let output = + format_source("fn main(){first*second/third+fourth-fifth*sixth}", &options).unwrap(); + expect![[r#" + fn main() { + first + * second + / third + + fourth + - fifth + * sixth + } + "#]] + .assert_eq(&output); + assert_eq!(format_source(&output, &options).unwrap(), output); +} + +#[test] +fn binary_precedence_tiers_stack_continuation_indents() { + let options = FormatOptions { + max_width: 24, + ..FormatOptions::default() + }; + let output = format_source( + "fn main(){alpha||bravo&&charlie==delta|echo^foxtrot&golf< (1, 2), - T![&&] => (3, 4), - T![==] | T![!=] => (5, 6), - T![<] | T![>] | T![<=] | T![>=] => (7, 8), - T![|] => (9, 10), - T![^] => (11, 12), - T![&] => (13, 14), - T![<<] | T![>>] | T![>>>] => (15, 16), - T![+] | T![-] => (17, 18), - T![*] | T![/] | T![%] => (19, 20), - _ => unreachable!(), - }; + let (left_binding_power, right_binding_power) = op + .binary_binding_power() + .expect("BINARY_OPS have binding power"); if left_binding_power < options.minimum_binding_power { return false; diff --git a/crates/rue-parser/src/syntax_kind.rs b/crates/rue-parser/src/syntax_kind.rs index 49f79939..115d54ba 100644 --- a/crates/rue-parser/src/syntax_kind.rs +++ b/crates/rue-parser/src/syntax_kind.rs @@ -494,6 +494,22 @@ impl SyntaxKind { T![>>], ]; + pub const fn binary_binding_power(self) -> Option<(u8, u8)> { + match self { + T![||] => Some((1, 2)), + T![&&] => Some((3, 4)), + T![==] | T![!=] => Some((5, 6)), + T![<] | T![>] | T![<=] | T![>=] => Some((7, 8)), + T![|] => Some((9, 10)), + T![^] => Some((11, 12)), + T![&] => Some((13, 14)), + T![<<] | T![>>] | T![>>>] => Some((15, 16)), + T![+] | T![-] => Some((17, 18)), + T![*] | T![/] | T![%] => Some((19, 20)), + _ => None, + } + } + pub fn is_trivia(&self) -> bool { matches!( self, @@ -632,3 +648,15 @@ impl SyntaxKind { } } } + +#[cfg(test)] +mod tests { + use super::SyntaxKind; + + #[test] + fn every_binary_operator_has_binding_power() { + for operator in SyntaxKind::BINARY_OPS { + assert!(operator.binary_binding_power().is_some()); + } + } +} diff --git a/examples/puzzles/partial_offer.rue b/examples/puzzles/partial_offer.rue index 0a1cb37b..3c968dfe 100644 --- a/examples/puzzles/partial_offer.rue +++ b/examples/puzzles/partial_offer.rue @@ -59,8 +59,8 @@ fn main( let new_amount = proof.amount - other_asset_amount - * precisions.price_precision - / precisions.precision; + * precisions.price_precision + / precisions.precision; let recreate = inline if new_amount > 0 { CreateCoin { From ca7b030a36e8394588b8e075af2521dc1d528b3c Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 17:35:53 -0400 Subject: [PATCH 13/21] Fix grouping of comments --- crates/rue-formatter/src/tests/comments.rs | 15 +++++++++++++++ examples/puzzles/cat.rue | 1 + examples/puzzles/cat.yaml | 2 +- examples/puzzles/partial_offer.rue | 1 + examples/puzzles/partial_offer.yaml | 2 +- examples/puzzles/royalty_split.rue | 1 + examples/puzzles/royalty_split.yaml | 2 +- examples/puzzles/singleton.rue | 1 + examples/puzzles/singleton.yaml | 4 ++-- 9 files changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/rue-formatter/src/tests/comments.rs b/crates/rue-formatter/src/tests/comments.rs index a00a6923..384ac7bf 100644 --- a/crates/rue-formatter/src/tests/comments.rs +++ b/crates/rue-formatter/src/tests/comments.rs @@ -59,6 +59,21 @@ fn comment_and_blank_line_boundaries() { ); } +#[test] +fn separated_file_banner_stays_detached_from_first_item() { + check( + "// This puzzle has not been audited.\n\n// Item documentation.\nstruct Example{value:Int}", + expect![[r#" + // This puzzle has not been audited. + + // Item documentation. + struct Example { + value: Int, + } + "#]], + ); +} + #[test] fn comments_inside_broken_delimiters() { let options = FormatOptions { diff --git a/examples/puzzles/cat.rue b/examples/puzzles/cat.rue index ae58db79..c42157ad 100644 --- a/examples/puzzles/cat.rue +++ b/examples/puzzles/cat.rue @@ -1,4 +1,5 @@ // This puzzle has not been audited or tested, and is for example purposes only. + // Information about the CAT, used for currying purposes. struct CatInfo { mod_hash: Bytes32, diff --git a/examples/puzzles/cat.yaml b/examples/puzzles/cat.yaml index c5fa0c6d..7fc76668 100644 --- a/examples/puzzles/cat.yaml +++ b/examples/puzzles/cat.yaml @@ -1,5 +1,5 @@ program: (a (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 27 ())) (a (i 57 (q 2 (i (= (a 39 (c 39 89)) 95) (q 2 -9 (c -9 (c (a 89 (c (c (c (a 39 (c 39 -65)) 19) (c 27 3071)) (c 13 (c 767 (c 24575 (c 2 (c -71 ()))))))) 2))) (q 8)) 1) (q 2 (i (all 13 (not 24575)) (q . 2) (q 8)) 1)) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a 19 (c 19 (c 767 (c 6143 ()))))) ())) (c (c (q . 61) (c (sha256 (coinid 5119 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 17) (sha256 (q . 2) (a 91 (c 91 (c 41 (c (sha256 (q . 1) 89) (c 11263 ()))))) (sha256 (q . 1))))) 23551) (q . -53) (a 19 (c 19 (c 13 (c (+ 6143 (- 11775 20) 12287) ()))))) ())) 8)) 1)) (c (c (a 21 (c (c 21 45) (c (a 47 95) (c 4 ())))) (a (i -65 (q 2 (i (= 1279 (coinid 319 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 8) (sha256 (q . 2) (a 45 (c 45 (c 20 (c (sha256 (q . 1) 44) (c 703 ()))))) (sha256 (q . 1))))) 1471)) (q 1 . 1) (q)) 1) (q)) 1)) 1)) (c (c (c 5 (c (sha256 (q . 1) 5) (c 11 ()))) (coinid 639 1407 2943)) 1)) (c (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (c (q 2 (i 5 (q 2 (i (a (i (= 17 (q . 51)) (q 2 (i 41 (q) (q 1 . 1)) 1) (q)) 1) (q 2 (q . 2) (c (a 4 (c 2 (c 13 (c 11 (c -71 (c 377 ())))))) 1)) (q 2 (q 4 (c 10 8) (c (+ 20 14) 28)) (c (c (a 4 (c 2 (c 13 7))) (a (i (= 17 (q . 51)) (q 4 (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 19) (sha256 (q . 2) (a 6 (c 6 (c 43 (c (sha256 (q . 1) 91) (c 41 ()))))) (sha256 (q . 1))))) 57)) 89) (q 2 (i (a (i (= 17 (q . 60)) (q 2 (i (all (= (strlen 41) (q . 33)) (= (substr 41 () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8) (q 4 9 ())) 1)) 1)) 1))) 1) (q 4 () (c () 15))) 1) (c (q 2 (i 3 (q 11 (q . 2) (sha256 (q . 260)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 5) (sha256 (q . 2) (a 2 (c 2 7)) (sha256 (q . 1))))) (q 11 (q . 257))) 1) (q 2 (i 5 (q 4 9 (a 2 (c 2 (c 13 7)))) (q . 7)) 1)))) 1)) -debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 351 ())) 2) (c (a (i (not (not (l (r (r 39))))) (q 2 (i (= (a 287 (c 287 (f (r (r 39))))) 383) (q 2 (q 2 1727 (c 1727 (c 2 5))) (c (a (f (r (r 39))) (c -9 (c 87 (c 3071 (c 0x017fff (c 2 (c (f (r (r (r 39)))) ()))))))) 1)) (q 8 (q . "assertion failed at cat.rue:149:9"))) 1) (q 2 (i (all 87 (= 0x017fff ())) (q . 2) (q 8 (q . "assertion failed at cat.rue:167:9"))) 1)) 1) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a -113 (c -113 (c 3071 (c 24575 ()))))) ())) (c (c (q . 61) (c (sha256 (concat (concat 91 (q . -53)) (a -113 (c -113 (c 87 (c 2 ())))))) ())) (f 19))) 1)) (c (+ (+ 12287 2) 24575) 1)) (c (- (f (r (r 1535))) (f (r 4))) 1)) (c (c (a 25 (c (c 25 (c 37 (c 45 (c 93 125)))) (c (a 47 95) (c 4 ())))) (c (a (i (l -65) (q 2 (i (= (f 767) (coinid (f -65) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r -65)) ())))))) (f (r (r -65))))) (q 1 . 1) (q)) 1) (q)) 1) (c (coinid (f 1535) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r 1535)) ())))))) (f (r (r 1535)))) (c (c 14 4) (c 10 767))))) 1)) (c (c (c 5 (c (sha256 (concat (q . 1) 5)) (c 11 ()))) (c (coinid (f 383) (f (r 383)) (f (r (r 383)))) (a 8 (c 8 23)))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 5)) (q 4 () (c () 15)) (q 2 (i (a (i (= (f (f 5)) (q . 51)) (q 2 (i (= (f (r (f 5))) ()) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (q 4 (f 2) (c (f (r 2)) (r (r 2)))) (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 (c (f (r (r (r (f 5))))) (c (f (r (r (r (r (f 5)))))) ())))))) 1)) (q 2 (q 4 (c (f 6) (f 4)) (c (+ (f (r 4)) (r 6)) (r (r 4)))) (c (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 15)))) (a (i (= (f (f 5)) (q . 51)) (q 2 (q 4 2 (f (r (r (f 11))))) (c (c (q . 51) (c (a 22 (c (c 10 (c 46 62)) (c (f 11) (c (f (r 11)) (c (sha256 (concat (q . 1) (f (r (r 11))))) (c (f (r (f 5))) ())))))) (c (f (r (r (f 5)))) (r (r (r (f 5))))))) 1)) (q 2 (i (a (i (= (f (f 5)) (q . 60)) (q 2 (i (all (= (strlen (f (r (f 5)))) (q . 33)) (= (substr (f (r (f 5))) () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8 (q . "raise called at cat.rue:234:13") (q . "Created coin announcements must not have the ring morph byte")) (q 4 (f 5) ())) 1)) 1)) 1))) 1)) 1)) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (q 2 (i (l 5) (q 4 (f 5) (a 2 (c 2 (c (r 5) 7)))) (q . 7)) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) +debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (q 2 (q 4 (c (q . 70) (c 351 ())) 2) (c (a (i (not (not (l (r (r 39))))) (q 2 (i (= (a 287 (c 287 (f (r (r 39))))) 383) (q 2 (q 2 1727 (c 1727 (c 2 5))) (c (a (f (r (r 39))) (c -9 (c 87 (c 3071 (c 0x017fff (c 2 (c (f (r (r (r 39)))) ()))))))) 1)) (q 8 (q . "assertion failed at cat.rue:150:9"))) 1) (q 2 (i (all 87 (= 0x017fff ())) (q . 2) (q 8 (q . "assertion failed at cat.rue:168:9"))) 1)) 1) 1)) (c (c (c (q . 60) (c (concat (q . -53) (a -113 (c -113 (c 3071 (c 24575 ()))))) ())) (c (c (q . 61) (c (sha256 (concat (concat 91 (q . -53)) (a -113 (c -113 (c 87 (c 2 ())))))) ())) (f 19))) 1)) (c (+ (+ 12287 2) 24575) 1)) (c (- (f (r (r 1535))) (f (r 4))) 1)) (c (c (a 25 (c (c 25 (c 37 (c 45 (c 93 125)))) (c (a 47 95) (c 4 ())))) (c (a (i (l -65) (q 2 (i (= (f 767) (coinid (f -65) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r -65)) ())))))) (f (r (r -65))))) (q 1 . 1) (q)) 1) (q)) 1) (c (coinid (f 1535) (a 45 (c (c 37 (c 93 125)) (c (f 4) (c (f (r 4)) (c (sha256 (concat (q . 1) (f (r (r 4))))) (c (f (r 1535)) ())))))) (f (r (r 1535)))) (c (c 14 4) (c 10 767))))) 1)) (c (c (c 5 (c (sha256 (concat (q . 1) 5)) (c 11 ()))) (c (coinid (f 383) (f (r 383)) (f (r (r 383)))) (a 8 (c 8 23)))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 5)) (q 4 () (c () 15)) (q 2 (i (a (i (= (f (f 5)) (q . 51)) (q 2 (i (= (f (r (f 5))) ()) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (q 4 (f 2) (c (f (r 2)) (r (r 2)))) (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 (c (f (r (r (r (f 5))))) (c (f (r (r (r (r (f 5)))))) ())))))) 1)) (q 2 (q 4 (c (f 6) (f 4)) (c (+ (f (r 4)) (r 6)) (r (r 4)))) (c (c (a 4 (c (c 4 (c 10 (c 22 (c 46 62)))) (c (r 5) (c 11 15)))) (a (i (= (f (f 5)) (q . 51)) (q 2 (q 4 2 (f (r (r (f 11))))) (c (c (q . 51) (c (a 22 (c (c 10 (c 46 62)) (c (f 11) (c (f (r 11)) (c (sha256 (concat (q . 1) (f (r (r 11))))) (c (f (r (f 5))) ())))))) (c (f (r (r (f 5)))) (r (r (r (f 5))))))) 1)) (q 2 (i (a (i (= (f (f 5)) (q . 60)) (q 2 (i (all (= (strlen (f (r (f 5)))) (q . 33)) (= (substr (f (r (f 5))) () (q . 1)) (q . -53))) (q 1 . 1) (q)) 1) (q)) 1) (q 8 (q . "raise called at cat.rue:235:13") (q . "Created coin announcements must not have the ring morph byte")) (q 4 (f 5) ())) 1)) 1)) 1))) 1)) 1)) (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (q 2 (i (l 5) (q 4 (f 5) (a 2 (c 2 (c (r 5) 7)))) (q . 7)) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) solution: (0x00f43ce9fcc63d5019e209c103e6b0aaf56bbe7fc7fafae5af7f5ee6887a8719 0xd622c62a7292ffee5cf2537a90360ca0b7337b76d7014ec042930c0a87592213 (q (g1_negate () -113 (a (q 2 (i 47 (q 8) (q 2 (i (= 45 2) () (q 8)) 1)) 1) (c (q . 0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861) 1)) ()) (g1_negate 0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001 1 (0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001))) () () 0x615236766bed52d7abaa41d270407f3ec852981852334b213bd8515924459a5d (0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861 0x1ecb863db5d2ae6c71e9a8b0741acb3e034e8164b8ca0e564d5fad8b9dc875d5 1) (0x895eb35a355941ba7f6a8679a73bb9b8b62cae2b04ef5351eda42583c0f2d861 0x130deb20b44082a68293974f8cab9c51e21f9a9f3005000168eb77e49e0fc378 1) () ()) output: ((70 0x615236766bed52d7abaa41d270407f3ec852981852334b213bd8515924459a5d) (modpow 0xcb7f53b5de05b4afe58ab663b952aa785fd9ad911564709bd8eacbf4c58ba1e589) (% 0x389f1ea7b9fab7a9294104eb3a181d662f2dbe9c43fbe52802ba9b7eef357e77) (g1_negate 0xc9644528436f44cd9b33282684b4964f55d5551cb3a970ab9cf8f536e0d72ad1 1 (0xb8705f94744e7fc30300ac9b12d306b283f5a702937ee99beabf665be6023001))) runtime_cost: 297861 diff --git a/examples/puzzles/partial_offer.rue b/examples/puzzles/partial_offer.rue index 3c968dfe..bd73e932 100644 --- a/examples/puzzles/partial_offer.rue +++ b/examples/puzzles/partial_offer.rue @@ -1,4 +1,5 @@ // This puzzle has not been audited or tested, and is for example purposes only. + struct Precisions { price_precision: Int, ...precision: Int, diff --git a/examples/puzzles/partial_offer.yaml b/examples/puzzles/partial_offer.yaml index c2281a75..92db40f0 100644 --- a/examples/puzzles/partial_offer.yaml +++ b/examples/puzzles/partial_offer.yaml @@ -1,5 +1,5 @@ program: (a (q 2 (q 4 (c (q . 63) (c (sha256 23 (a 5 (c 5 (c 1279 (c (c 47 (c (a (i (> 1535 -65) (q . 1535) (q 8)) 1) (c (c 47 ()) ()))) ()))))) ())) (c (c (q . 70) (c (coinid 1279 (a 11 (c 2815 6143)) 5887) ())) (c (i (> 4 ()) (c (q . 51) (c 2815 (c 4 (c (c 2815 ()) ())))) (q 1)) (i (a (i 3071 (q 2 (i (> 11263 (q . -1)) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 6) 6)))) (c (c (- 2943 (/ (* 767 319) 447)) (a 47 4095)) 1)) (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) 1)) -debug_program: (a (q 2 (q 2 (q 4 45 (c 61 (c 4 6))) (c (c (i (> 4 ()) (c (q . 51) (c (f (r 767)) (c 4 (c (c (f (r 767)) ()) ())))) (c (q . 1) ())) (i (a (i (not (not (l 3071))) (q 2 (i (> (f (r 3071)) (- () (q . 1))) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 10) 10)) 1)) (c (c (- (f (r (r 383))) (/ (* 767 (f -65)) (r -65))) (c (a 47 4095) (c (c (q . 63) (c (sha256 (concat 11 (a 2 (c 2 (c (f 383) (c (c 23 (c (a (i (> 767 95) (q . 767) (q 8 (q . "raise called at partial_offer.rue:44:29") (q . "Other asset amount is too small"))) 1) (c (c 23 ()) ()))) ())))))) ())) (c (q . 70) (c (coinid (f 383) (a 5 (c (f (r 383)) 3071)) (f (r (r 383)))) ()))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) 1)) +debug_program: (a (q 2 (q 2 (q 4 45 (c 61 (c 4 6))) (c (c (i (> 4 ()) (c (q . 51) (c (f (r 767)) (c 4 (c (c (f (r 767)) ()) ())))) (c (q . 1) ())) (i (a (i (not (not (l 3071))) (q 2 (i (> (f (r 3071)) (- () (q . 1))) (q 1 . 1) (q)) 1) (q)) 1) (c (c (q . 51) 3071) 10) 10)) 1)) (c (c (- (f (r (r 383))) (/ (* 767 (f -65)) (r -65))) (c (a 47 4095) (c (c (q . 63) (c (sha256 (concat 11 (a 2 (c 2 (c (f 383) (c (c 23 (c (a (i (> 767 95) (q . 767) (q 8 (q . "raise called at partial_offer.rue:45:29") (q . "Other asset amount is too small"))) 1) (c (c 23 ()) ()))) ())))))) ())) (c (q . 70) (c (coinid (f 383) (a 5 (c (f (r 383)) 3071)) (f (r (r 383)))) ()))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) 1)) solution: () byte_cost: 5100000 clvm_error: path into atom diff --git a/examples/puzzles/royalty_split.rue b/examples/puzzles/royalty_split.rue index ff4a2939..257993d3 100644 --- a/examples/puzzles/royalty_split.rue +++ b/examples/puzzles/royalty_split.rue @@ -1,4 +1,5 @@ // This puzzle has not been audited or tested, and is for example purposes only. + struct Payout { puzzle_hash: Bytes32, share: Int, diff --git a/examples/puzzles/royalty_split.yaml b/examples/puzzles/royalty_split.yaml index 7026275b..eba267fc 100644 --- a/examples/puzzles/royalty_split.yaml +++ b/examples/puzzles/royalty_split.yaml @@ -1,5 +1,5 @@ program: (a (q 4 (q 60 ()) (c (c (q . 73) (c 11 ())) (a 4 (c 2 (c (c 5 23) (c 11 (c () 11))))))) (c (c (q 2 (i 9 (q 2 6 (c 2 (c 9 (c (c (/ (* 11 81) 13) 31) (c 11 (c 13 23)))))) (q 2 (i (= 13 23) (q) (q 8)) 1)) 1) (q 4 (c (q . 51) (c 17 (c (i 13 19 27) (c (c 17 ()) ())))) (a 4 (c 2 (c (c 13 47) (c 23 (c (+ 63 41) (- 27 19)))))))) 1)) -debug_program: (a (q 4 (c (q . 60) (c () ())) (c (c (q . 73) (c 11 ())) (a 4 (c (c 4 (c 10 14)) (c (c 5 23) (c 11 (c () 11))))))) (c (c (q 2 (i (not (l 9)) (q 2 (i (= 13 23) (q) (q 8 (q . "raise called at royalty_split.rue:31:9") (q . "Share sum doesn't match total"))) 1) (q 2 14 (c (c 4 (c 10 14)) (c 9 (c (c (a 10 (c 11 (c (f (r (f 9))) 13))) 31) (c 11 (c 13 23))))))) 1) (c (q 19 (* 2 5) 7) (q 2 (q 4 2 (a 9 (c (c 9 (c 21 29)) (c (c (r 11) 95) (c 47 (c (+ 127 (f (r (f 11)))) (- 55 39))))))) (c (c (q . 51) (c (f (f 5)) (c (i (not (l (r 5))) 27 19) (c (c (f (f 5)) ()) ())))) 1)))) 1)) +debug_program: (a (q 4 (c (q . 60) (c () ())) (c (c (q . 73) (c 11 ())) (a 4 (c (c 4 (c 10 14)) (c (c 5 23) (c 11 (c () 11))))))) (c (c (q 2 (i (not (l 9)) (q 2 (i (= 13 23) (q) (q 8 (q . "raise called at royalty_split.rue:32:9") (q . "Share sum doesn't match total"))) 1) (q 2 14 (c (c 4 (c 10 14)) (c 9 (c (c (a 10 (c 11 (c (f (r (f 9))) 13))) 31) (c 11 (c 13 23))))))) 1) (c (q 19 (* 2 5) 7) (q 2 (q 4 2 (a 9 (c (c 9 (c 21 29)) (c (c (r 11) 95) (c 47 (c (+ 127 (f (r (f 11)))) (- 55 39))))))) (c (c (q . 51) (c (f (f 5)) (c (i (not (l (r 5))) 27 19) (c (c (f (f 5)) ()) ())))) 1)))) 1)) solution: (((0x3816a97150946c7745457f45a4fed6c16890637847b2df95f383e439758732d7 80) (0x09731fc56bff8031dc9a16f60dcb6f6462960cd76d4b5233dd5bc21697d7fca7 20)) 1000 100) output: ((modpow ()) (73 1000) (g1_negate 0x3816a97150946c7745457f45a4fed6c16890637847b2df95f383e439758732d7 800 (0x3816a97150946c7745457f45a4fed6c16890637847b2df95f383e439758732d7)) (g1_negate 0x09731fc56bff8031dc9a16f60dcb6f6462960cd76d4b5233dd5bc21697d7fca7 200 (0x09731fc56bff8031dc9a16f60dcb6f6462960cd76d4b5233dd5bc21697d7fca7))) runtime_cost: 14926 diff --git a/examples/puzzles/singleton.rue b/examples/puzzles/singleton.rue index 1210996f..62fe4a8d 100644 --- a/examples/puzzles/singleton.rue +++ b/examples/puzzles/singleton.rue @@ -1,4 +1,5 @@ // This puzzle has not been audited or tested, and is for example purposes only. + struct Singleton { mod_hash: Bytes32, launcher_id: Bytes32, diff --git a/examples/puzzles/singleton.yaml b/examples/puzzles/singleton.yaml index 71d3cb5e..e7db8641 100644 --- a/examples/puzzles/singleton.yaml +++ b/examples/puzzles/singleton.yaml @@ -1,5 +1,5 @@ program: (a (q 2 (q 2 (i (all (logand 95 (q . 1)) (any 4 (= 6 43))) (q 4 (c (q . 73) (c 95 ())) (c (c (q . 71) (c 6 ())) (a 9 (c 5 (c 11 (c (a 23 -65) ())))))) (q 8)) 1) (c (a (i 119 (q 4 (q . 1) (coinid 39 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 9) (sha256 (q . 2) (a 14 (c 14 (c (a 10 (c 10 5)) (c 87 ())))) (sha256 (q . 1))))) -73)) (q 4 () (coinid 39 29 87))) 1) 1)) (c (c (q 2 (i 11 (q 2 (i (a (i (= 35 (q . 51)) (q 2 (i (logand -77 (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i 15 (q 8) (q 2 (q 2 (i (= 359 (q . -113)) (q . 2) (q 4 (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 19) (sha256 (q . 2) (a 29 (c 29 (c (a 21 (c 21 11)) (c -89 ())))) (sha256 (q . 1))))) -25)) 2)) 1) (c (a 4 (c 2 (c 5 (c 27 (q . 1))))) 1))) 1) (q 4 19 (a 4 (c 2 (c 5 (c 27 ())))))) 1) (q 2 (i 15 (q) (q 8)) 1)) 1) (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (q 2 (i 3 (q 11 (q . 2) (sha256 (q . 260)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 5) (sha256 (q . 2) (a 2 (c 2 7)) (sha256 (q . 1))))) (q 11 (q . 257))) 1))) 1)) -debug_program: (a (q 2 (q 2 (q 2 (i (all 13 2) (q 2 (q 4 4 (c 10 14)) (c (c (c (q . 73) (c -65 ())) (c (c (q . 71) (c (r 9) ())) (a 35 (c (c (c 35 51) (c 43 (c 91 (c -69 -5)))) (c 23 (c (a 47 383) ())))))) 1)) (q 8 (q . "assertion failed at singleton.rue:57:5"))) 1) (c (any (f 4) (= (r 4) (f (r 11)))) 1)) (c (c (a (i (r (r 23)) (q 4 (q . 1) (coinid (f 23) (a 22 (c (c 10 (c 46 62)) (c (f 5) (c (a 12 (c 12 5)) (c (f (r 23)) ()))))) (f (r (r 23))))) (q 4 () (coinid (f 23) (r (r 5)) (f (r 23))))) 1) (= (% 47 (q . 2)) (q . 1))) 1)) (c (c (c (q 2 (i (not (l 11)) (q 2 (i 15 (q) (q 8 (q . "assertion failed at singleton.rue:78:9"))) 1) (q 2 (i (a (i (= (f (f 11)) (q . 51)) (q 2 (i (= (% (f (r (r (f 11)))) (q . 2)) (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i (not 15) (q 2 (q 2 (i (= (f (r (r (f 23)))) (- () (q . 113))) (q . 2) (q 2 (q 4 2 5) (c (c (q . 51) (c (a 45 (c (c 21 (c 93 125)) (c (f 11) (c (a 25 (c 25 11)) (c (f (r (f 23))) ()))))) (c (f (r (r (f 23)))) (r (r (r (f 23))))))) 1))) 1) (c (a 8 (c (c (c 8 12) (c 10 (c 22 (c 46 62)))) (c 5 (c (r 11) (q . 1))))) 1)) (q 8 (q . "assertion failed at singleton.rue:85:9"))) 1) (q 4 (f 11) (a 8 (c (c (c 8 12) (c 10 (c 22 (c 46 62)))) (c 5 (c (r 11) ())))))) 1)) 1) (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) +debug_program: (a (q 2 (q 2 (q 2 (i (all 13 2) (q 2 (q 4 4 (c 10 14)) (c (c (c (q . 73) (c -65 ())) (c (c (q . 71) (c (r 9) ())) (a 35 (c (c (c 35 51) (c 43 (c 91 (c -69 -5)))) (c 23 (c (a 47 383) ())))))) 1)) (q 8 (q . "assertion failed at singleton.rue:58:5"))) 1) (c (any (f 4) (= (r 4) (f (r 11)))) 1)) (c (c (a (i (r (r 23)) (q 4 (q . 1) (coinid (f 23) (a 22 (c (c 10 (c 46 62)) (c (f 5) (c (a 12 (c 12 5)) (c (f (r 23)) ()))))) (f (r (r 23))))) (q 4 () (coinid (f 23) (r (r 5)) (f (r 23))))) 1) (= (% 47 (q . 2)) (q . 1))) 1)) (c (c (c (q 2 (i (not (l 11)) (q 2 (i 15 (q) (q 8 (q . "assertion failed at singleton.rue:79:9"))) 1) (q 2 (i (a (i (= (f (f 11)) (q . 51)) (q 2 (i (= (% (f (r (r (f 11)))) (q . 2)) (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i (not 15) (q 2 (q 2 (i (= (f (r (r (f 23)))) (- () (q . 113))) (q . 2) (q 2 (q 4 2 5) (c (c (q . 51) (c (a 45 (c (c 21 (c 93 125)) (c (f 11) (c (a 25 (c 25 11)) (c (f (r (f 23))) ()))))) (c (f (r (r (f 23)))) (r (r (r (f 23))))))) 1))) 1) (c (a 8 (c (c (c 8 12) (c 10 (c 22 (c 46 62)))) (c 5 (c (r 11) (q . 1))))) 1)) (q 8 (q . "assertion failed at singleton.rue:86:9"))) 1) (q 4 (f 11) (a 8 (c (c (c 8 12) (c 10 (c 22 (c 46 62)))) (c 5 (c (r 11) ())))))) 1)) 1) (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1)) (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) 1)) solution: ((0x7faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9f 0x1dee7745609c0ebcf4e1cfe01f2b2e7f4deb122eb735217e7af08029ba322b13 . 0xeff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9) (a (q 2 (q 2 62 (c 2 (c 5 (c (a 47 95) (c () (c (c (c 11 (c 23 ())) (q ())) (q ()))))))) (c (q ((a . 51) 4 1 . 1) (a 2 (i 5 (q 2 26 (c 2 (c 13 (c (sha256 18 (sha256 44 20) (sha256 18 (sha256 18 (sha256 44 60) 9) (sha256 18 11 (sha256 44 ())))) ())))) (q . 11)) 1) (sha256 18 (sha256 44 16) (sha256 18 (sha256 18 (sha256 44 60) 5) (sha256 18 (a 26 (c 2 (c 7 (c (sha256 44 44) ())))) (sha256 44 ())))) (a (i (l 5) (q 11 (q . 2) (a 46 (c 2 (c 9 ()))) (a 46 (c 2 (c 13 ())))) (q 11 (q . 1) 5)) 1) 2 (i 11 (q 2 (i (= 35 24) (q 2 (i (logand -77 44) (q 2 (i (not 23) (q 2 62 (c 2 (c 5 (c 27 (c 51 (c 47 (c 95 ()))))))) (q 8)) 1) (q 4 19 (a 62 (c 2 (c 5 (c 27 (c 23 (c 47 (c 95 ()))))))))) 1) (q 2 (i (= 35 (q . -24)) (q 2 62 (c 2 (c 5 (c 27 (c 23 (c (a (i (all (= (a 46 (c 2 (c 83 ()))) 335) (not 95)) (q 2 83 (c -113 (c 335 (c -77 ())))) (q 8)) 1) (c 44 ()))))))) (q 4 19 (a 62 (c 2 (c 5 (c 27 (c 23 (c 47 (c 95 ()))))))))) 1)) 1) (q 4 (c 24 (c (a 22 (c 2 (c 5 (c 39 (c (sha256 44 335) (c (a 46 (c 2 (c -113 ()))) (c (sha256 44 5) ()))))))) 55)) -81)) 1) 1)) (c (q . 0xa04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2) (c (q (117 "https://chia-monster.ggames.mobi/chia-monster/images/warriors/females/legendary_warrior_v2.jpg") (104 . 0x9beb5d62e9708e3178414dcf7340bc3547fb6fdeadcf93a397d87dbb432bd923) (28021 "https://chia-monster.ggames.mobi/api/cmt/meta/warrior/6f1cb204-b594-4516-af4c-f9f55e30fd39.json") (27765 "https://chia-monster.ggames.mobi/chia-monster/license.txt") (29550 . 1) (29556 . 1) (28008 . 0x347340029cb9234e7a43d75d4c5649136ac11a97cd75c1137da2444868cab52b) (27752 . 0x724672b619bcbb77d46cba416327376944366d6c71e87abe49be19abbca0444f)) (c (q . 0xfe8a4b4e27a2e29a4d3fc7ce9d527adbcaccbab6ada3903ccf3ba9a769d2d78b) (c (q 2 (q 2 (q 2 38 (c 2 (c 5 (c 23 (c 11 (c (a 47 95) ())))))) (c (q ((-21172 2 . 51) (keccak256 . 4) -10 . 1) ((q . 2) (a (i 5 (q 2 42 (c 2 (c 13 (c (sha256 50 (sha256 60 52) (sha256 50 (sha256 50 (sha256 60 34) 9) (sha256 50 11 (sha256 60 ())))) ())))) (q . 11)) 1) 4 (c 56 (c (a 54 (c 2 (c 5 (c 39 (c (a 46 (c 2 (c (a (i -81 (q . -81) (q . 11)) 1) ()))) (c (sha256 60 79) (c (sha256 60 5) ()))))))) 55)) 367) ((a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c 47 (q () ())))))))) 11 50 (sha256 60 40) (sha256 50 (sha256 50 (sha256 60 34) 5) (sha256 50 (a 42 (c 2 (c 7 (c (sha256 60 60) ())))) (sha256 60 ())))) (a (i (l 5) (q 11 (q . 2) (a 46 (c 2 (c 9 ()))) (a 46 (c 2 (c 13 ())))) (q 11 (q . 1) 5)) 1) 2 (i 95 (q 2 (i (= 287 56) (q 2 (i (= (logand 1439) 60) (q 2 (i (not -65) (q 2 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c 415 (c 383 ()))))))))) (q 8)) 1) (q 4 -97 (a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c -65 (c 383 ()))))))))))) 1) (q 2 (i (= 287 44) (q 2 (i (not 383) (q 4 (c 36 (c (concat 16 (a 46 (c 2 (c 415 ())))) ())) (a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c -65 (c (a 11 (c 23 (c 47 (c 415 ())))) ())))))))))) (q 8)) 1) (q 2 (i (= 287 36) (q 2 (i (not (a (i (= (q . 34) (strlen 671)) (q 2 (i (= (substr 671 () (q . 2)) 16) (q 1 . 1) ()) 1) ()) 1)) (q 4 -97 (a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c -65 (c 383 ())))))))))) (q 8)) 1) (q 4 -97 (a 62 (c 2 (c 5 (c 11 (c 23 (c 47 (c -33 (c -65 (c 383 ()))))))))))) 1)) 1)) 1) (q 2 58 (c 2 (c 5 (c 11 (c -65 (c (a (i 383 (q . 383) (q 2 11 (c 23 (c 47 (q ()))))) 1) ()))))))) 1) 1)) (c (q . 0xc5abea79afaa001b5427dfa0c8cf42ca6f38f5841b78f9b3c252733eb2de2726) (c (q) (c (q 2 (q 2 (q 2 (i -65 (q 4 319 (c () (c (a (i (all 319 (not (= 319 47))) (q 4 (c 16 (c (sha256 (a 46 (c 2 (c 9 (c 1471 (c (a 62 (c 2 (c (c 9 (c 319 29)) ()))) ()))))) 21) ())) (a 22 (c 2 (c 11 (c 23 (c 703 (c 21 ()))))))) (q 2 22 (c 2 (c 11 (c 23 (c 703 (c 21 ()))))))) 1) ()))) (q 4 47 (q () ()))) 1) (c (q ((63 . 2) 4 1 . 1) (10000 2 2 (i 5 (q 2 58 (c 2 (c 13 (c (sha256 42 (sha256 44 20) (sha256 42 (sha256 42 (sha256 44 60) 9) (sha256 42 11 (sha256 44 ())))) ())))) (q . 11)) 1) (a (i 23 (q 4 (c 16 (c (sha256 -89 (a 62 (c 2 (c (c 47 (c (c 5 (c (f (divmod (* 71 11) 18)) (c (c 5 ()) ()))) ())) ())))) ())) (a 22 (c 2 (c 5 (c 11 (c 55 (c 47 ()))))))) ()) 1) (sha256 42 (sha256 44 24) (sha256 42 (sha256 42 (sha256 44 60) 5) (sha256 42 (a 58 (c 2 (c 7 (c (sha256 44 44) ())))) (sha256 44 ())))) 2 (i (l 5) (q 11 (q . 2) (a 62 (c 2 (c 9 ()))) (a 62 (c 2 (c 13 ())))) (q 11 (q . 1) 5)) 1) 1)) (c (q 0x7faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9f 0x1dee7745609c0ebcf4e1cfe01f2b2e7f4deb122eb735217e7af08029ba322b13 . 0xeff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9) (c (q . 0x15865265653a7cb6f807a227aa9ad05498babe13ec906155b2676d5f92f84d40) (c (q) 1)))) (c (q 2 (q 2 (q 2 (i 11 (q 2 (i (= 5 (point_add 11 (pubkey_for_exp (sha256 11 (a 6 (c 2 (c 23 ()))))))) (q 2 23 47) (q 8)) 1) (q 4 (c 4 (c 5 (c (a 6 (c 2 (c 23 ()))) ()))) (a 23 47))) 1) (c (q 50 2 (i (l 5) (q 11 (q . 2) (a 6 (c 2 (c 9 ()))) (a 6 (c 2 (c 13 ())))) (q 11 (q . 1) 5)) 1) 1)) (c (q . 0xafa76c68cfb723922e67dbfa6bda38fd64609b41d2a4498df49a6c839ed8b2d1c042f5961a2ab9b318cc64aef72364a5) 1)) 1))))) 1))))) (0xdebedabb069cb2564157ff1ca5aa80b3cb3d7eb930f52ff1a40b8b78c84f4125 0x9fc9c96a5f2417e8617ea847651ceaf4789e6528a55101457d120c630a8ac030 1) 1 (((() (q (g1_negate 0x291c295548f95c2462caa05165f67e9f6d806153856edcbb84290162e7f810c5 1 (0x291c295548f95c2462caa05165f67e9f6d806153856edcbb84290162e7f810c5)) (-10 () () ())) ())))) output: ((73 1) (71 0x82acf3e76d5657e98438ad4dc0fcaf84f8e194d78313206ead021e2d5afe5e3a) (g1_multiply 0xafa76c68cfb723922e67dbfa6bda38fd64609b41d2a4498df49a6c839ed8b2d1c042f5961a2ab9b318cc64aef72364a5 0x8f25313e125d83841401ae4bf3831b674256c0fa77c8b30f12dd5a7bce846e75) (keccak256 0xad4c53b883241fddfd1c01b608bd4b6633e3fefd950e27c194aab24e6eb0a49bebd2) (g1_negate 0x899b958268f46b62cc8d2148fef14ce74e8220c507ee9a06a78924fb9efc4d75 1 (0x291c295548f95c2462caa05165f67e9f6d806153856edcbb84290162e7f810c5))) runtime_cost: 1370969 @@ -8,7 +8,7 @@ total_cost: 11270969 tests: - name: tests program: (a (q 2 (q 2 (q 2 (q 2 (i (= (a 39 (c 39 (a -73 (c 87 (c 39 (c -9 (c 2 (c (q . 1) (c (c 19 (q ())) (c (q . 1) (c (c (c (q . 51) (c 59 (q 1))) ()) ()))))))))))) (a 39 (c 39 (c (q 73 1) (c (c (q . 71) (c 5 ())) (c (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 4) (sha256 (q . 2) (a -9 (c -9 (c (a 39 (c 39 2)) (c 59 ())))) (sha256 (q . 1))))) (q 1))) ())))))) (q) (q 8)) 1) (c (c (a 19 (c 19 (c (q . 2) (c (c (q . 1) 91) (c (c (q . 4) (c (c (q . 1) 43) (c (c (q . 4) (c (c (q . 1) 19) (c (c (q . 4) (c (c (q . 1) 123) (c (q . 1) ()))) ()))) ()))) ()))))) (c 2 21)) 1)) (c (coinid 4 10 ()) 1)) (c (c (sha256 (q . "parent_parent_coin_info")) (c (sha256 (q . "launcher_puzzle")) (sha256 (q . "p2_puzzle_hash")))) 1)) (c (c (q 2 (i (l 3) (q 11 (q . 2) (a 2 (c 2 5)) (a 2 (c 2 7))) (q 11 (q . 1) 3)) 1) (c (q 2 (i 11 (q 2 (i (a (i (= 35 (q . 51)) (q 2 (i (logand -77 (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i 15 (q 8) (q 2 (q 2 (i (= 359 (q . -113)) (q . 2) (q 4 (c (q . 51) (c (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 19) (sha256 (q . 2) (a 29 (c 29 (c (a 9 (c 9 11)) (c -89 ())))) (sha256 (q . 1))))) -25)) 2)) 1) (c (a 10 (c 2 (c 5 (c 27 (q . 1))))) 1))) 1) (q 4 19 (a 10 (c 2 (c 5 (c 27 ())))))) 1) (q 2 (i 15 (q) (q 8)) 1)) 1) (c (q 2 (q 2 (i (all (logand 383 (q . 1)) (any 4 (= 6 -81))) (q 4 (c (q . 73) (c 383 ())) (c (c (q . 71) (c 6 ())) (a 5 (c (c 11 (c 5 23)) (c 47 (c (a 95 767) ())))))) (q 8)) 1) (c (a (i 479 (q 4 (q . 1) (coinid -97 (sha256 (q . 2) (sha256 (q . 258)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 39) (sha256 (q . 2) (a 11 (c 11 (c (a 5 (c 5 23)) (c 351 ())))) (sha256 (q . 1))))) 735)) (q 4 () (coinid -97 119 351))) 1) 1)) (q 2 (i 3 (q 11 (q . 2) (sha256 (q . 260)) (sha256 (q . 2) (sha256 (q . 2) (sha256 (q . 257)) 5) (sha256 (q . 2) (a 2 (c 2 7)) (sha256 (q . 1))))) (q 11 (q . 257))) 1)))) ())) - debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (i (= (a -113 (c -113 2)) (a -113 (c -113 (c (c (q . 73) (c (q . 1) ())) (c (c (q . 71) (c 19 ())) (c (c (q . 51) (c (a 367 (c (c 431 (c 751 1007)) (c (f 5) (c (a -113 (c -113 5)) (c 87 ()))))) (c (q . 1) ()))) ())))))) (q) (q 8 (q . "assertion failed at singleton.rue:125:5"))) 1) (c (a -105 (c 103 (c 71 (c -73 (c -41 (c 375 (c 503 (c 2 (c 59 (c 13 (c (q . 1) (c (c (c (q . 51) (c 43 (c (q . 1) ()))) ()) ())))))))))))) 1)) (c (c (a 35 (c 35 (c (q . 2) (c (c (q . 1) 75) (c (c (q . 4) (c (c (q . 1) 51) (c (c (q . 4) (c (c (q . 1) 35) (c (c (q . 4) (c (c (q . 1) 91) (c (c (q . 4) (c (c (q . 1) 107) (c (c (q . 4) (c (c (q . 1) -69) (c (c (q . 4) (c (c (q . 1) -5) (c (q . 1) ()))) ()))) ()))) ()))) ()))) ()))) ()))))) (c 4 25)) 1)) (c (c (coinid 8 12 ()) (c 8 (c () ()))) 1)) (c (c (c (sha256 (q . "parent_parent_coin_info")) (sha256 (q . "launcher_puzzle"))) (c (sha256 (q . "p2_puzzle_hash")) (c (q . 2) (c (c (q . 1) (q . 1)) (c (q . 1) ()))))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 11)) (q 2 (i 15 (q) (q 8 (q . "assertion failed at singleton.rue:78:9"))) 1) (q 2 (i (a (i (= (f (f 11)) (q . 51)) (q 2 (i (= (% (f (r (r (f 11)))) (q . 2)) (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i (not 15) (q 2 (q 2 (i (= (f (r (r (f 23)))) (- () (q . 113))) (q . 2) (q 2 (q 4 2 5) (c (c (q . 51) (c (a 93 (c (c 45 (c -67 -3)) (c (f 11) (c (a 9 (c 9 11)) (c (f (r (f 23))) ()))))) (c (f (r (r (f 23)))) (r (r (r (f 23))))))) 1))) 1) (c (a 10 (c (c 4 (c 10 (c 22 (c 46 (c 94 126))))) (c 5 (c (r 11) (q . 1))))) 1)) (q 8 (q . "assertion failed at singleton.rue:85:9"))) 1) (q 4 (f 11) (a 10 (c (c 4 (c 10 (c 22 (c 46 (c 94 126))))) (c 5 (c (r 11) ())))))) 1)) 1)) (c (c (q 2 (q 2 (q 2 (i (all 13 2) (q 2 (q 4 4 (c 10 14)) (c (c (c (q . 73) (c 6143 ())) (c (c (q . 71) (c (r 9) ())) (a 11 (c (c 23 (c 11 (c 95 (c 47 (c -65 383))))) (c 767 (c (a 1535 12287) ())))))) 1)) (q 8 (q . "assertion failed at singleton.rue:57:5"))) 1) (c (any (f 4) (= (r 4) (f (r 383)))) 1)) (c (c (a (i (r (r 767)) (q 4 (q . 1) (coinid (f 767) (a 11 (c (c 23 (c 47 95)) (c (f -65) (c (a 5 (c 5 -65)) (c (f (r 767)) ()))))) (f (r (r 767))))) (q 4 () (coinid (f 767) (r (r -65)) (f (r 767))))) 1) (= (% 1535 (q . 2)) (q . 1))) 1)) (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) ())) + debug_program: (a (q 2 (q 2 (q 2 (q 2 (q 2 (i (= (a -113 (c -113 2)) (a -113 (c -113 (c (c (q . 73) (c (q . 1) ())) (c (c (q . 71) (c 19 ())) (c (c (q . 51) (c (a 367 (c (c 431 (c 751 1007)) (c (f 5) (c (a -113 (c -113 5)) (c 87 ()))))) (c (q . 1) ()))) ())))))) (q) (q 8 (q . "assertion failed at singleton.rue:126:5"))) 1) (c (a -105 (c 103 (c 71 (c -73 (c -41 (c 375 (c 503 (c 2 (c 59 (c 13 (c (q . 1) (c (c (c (q . 51) (c 43 (c (q . 1) ()))) ()) ())))))))))))) 1)) (c (c (a 35 (c 35 (c (q . 2) (c (c (q . 1) 75) (c (c (q . 4) (c (c (q . 1) 51) (c (c (q . 4) (c (c (q . 1) 35) (c (c (q . 4) (c (c (q . 1) 91) (c (c (q . 4) (c (c (q . 1) 107) (c (c (q . 4) (c (c (q . 1) -69) (c (c (q . 4) (c (c (q . 1) -5) (c (q . 1) ()))) ()))) ()))) ()))) ()))) ()))) ()))))) (c 4 25)) 1)) (c (c (coinid 8 12 ()) (c 8 (c () ()))) 1)) (c (c (c (sha256 (q . "parent_parent_coin_info")) (sha256 (q . "launcher_puzzle"))) (c (sha256 (q . "p2_puzzle_hash")) (c (q . 2) (c (c (q . 1) (q . 1)) (c (q . 1) ()))))) 1)) (c (c (c (q 2 (i (not (l 3)) (q 11 (concat (q . 1) 3)) (q 11 (concat (concat (q . 2) (a 2 (c 2 (f 3)))) (a 2 (c 2 (r 3)))))) 1) (q 2 (i (not (l 11)) (q 2 (i 15 (q) (q 8 (q . "assertion failed at singleton.rue:79:9"))) 1) (q 2 (i (a (i (= (f (f 11)) (q . 51)) (q 2 (i (= (% (f (r (r (f 11)))) (q . 2)) (q . 1)) (q 1 . 1) (q)) 1) (q)) 1) (q 2 (i (not 15) (q 2 (q 2 (i (= (f (r (r (f 23)))) (- () (q . 113))) (q . 2) (q 2 (q 4 2 5) (c (c (q . 51) (c (a 93 (c (c 45 (c -67 -3)) (c (f 11) (c (a 9 (c 9 11)) (c (f (r (f 23))) ()))))) (c (f (r (r (f 23)))) (r (r (r (f 23))))))) 1))) 1) (c (a 10 (c (c 4 (c 10 (c 22 (c 46 (c 94 126))))) (c 5 (c (r 11) (q . 1))))) 1)) (q 8 (q . "assertion failed at singleton.rue:86:9"))) 1) (q 4 (f 11) (a 10 (c (c 4 (c 10 (c 22 (c 46 (c 94 126))))) (c 5 (c (r 11) ())))))) 1)) 1)) (c (c (q 2 (q 2 (q 2 (i (all 13 2) (q 2 (q 4 4 (c 10 14)) (c (c (c (q . 73) (c 6143 ())) (c (c (q . 71) (c (r 9) ())) (a 11 (c (c 23 (c 11 (c 95 (c 47 (c -65 383))))) (c 767 (c (a 1535 12287) ())))))) 1)) (q 8 (q . "assertion failed at singleton.rue:58:5"))) 1) (c (any (f 4) (= (r 4) (f (r 383)))) 1)) (c (c (a (i (r (r 767)) (q 4 (q . 1) (coinid (f 767) (a 11 (c (c 23 (c 47 95)) (c (f -65) (c (a 5 (c 5 -65)) (c (f (r 767)) ()))))) (f (r (r 767))))) (q 4 () (coinid (f 767) (r (r -65)) (f (r 767))))) 1) (= (% 1535 (q . 2)) (q . 1))) 1)) (q 2 (i (not (l 3)) (q 11 (concat (q . 1) (q . 1))) (q 2 6 (c (f 3) (a 4 (c (c 4 6) (r 3)))))) 1)) (c (q 2 14 (c 5 (a 4 (c (c 4 10) 7)))) (c (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 4)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))) (q 11 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 2)))) (sha256 (concat (concat (q . 2) (sha256 (concat (concat (q . 2) (sha256 (concat (q . 1) (q . 1)))) 2))) (sha256 (concat (concat (q . 2) 3) (sha256 (concat (q . 1) ())))))))))))) ())) output: () runtime_cost: 1288216 byte_cost: 17160000 From d5bcfc51a9e657e6a57950c77210cb4c52137d5c Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 17:42:19 -0400 Subject: [PATCH 14/21] More changes --- crates/rue-formatter/src/ordering.rs | 33 +++++++++++++++++++------ crates/rue-formatter/src/tests.rs | 17 +++++++++++++ tests/imports/import_order.rue | 1 - tests/imports/unresolved_imports.rue | 6 ++--- tests/imports/unresolved_imports.yaml | 6 ++--- tests/projects/external_import/main.rue | 1 - tests/symbols/closures.rue | 1 - tests/symbols/closures.yaml | 2 +- tests/symbols/complex_constants.rue | 3 --- tests/symbols/invalid_recursion.rue | 3 --- tests/symbols/invalid_recursion.yaml | 6 ++--- tests/symbols/super_errors.rue | 4 +-- tests/symbols/super_errors.yaml | 18 +++++++------- tests/warnings/unused_imports.rue | 24 ++++++------------ tests/warnings/unused_imports.yaml | 22 ++++++++--------- tests/warnings/unused_symbols.rue | 2 -- tests/warnings/unused_symbols.yaml | 4 +-- tests/warnings/unused_types.rue | 9 ------- tests/warnings/unused_types.yaml | 8 +++--- 19 files changed, 85 insertions(+), 85 deletions(-) diff --git a/crates/rue-formatter/src/ordering.rs b/crates/rue-formatter/src/ordering.rs index 497f0598..7f000990 100644 --- a/crates/rue-formatter/src/ordering.rs +++ b/crates/rue-formatter/src/ordering.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, fmt::Write}; -use rue_ast::{AstDocument, AstItem, AstNode}; +use rue_ast::{AstDocument, AstFunctionItem, AstItem, AstNode}; use rue_parser::{SyntaxKind, SyntaxNode, T}; use crate::{ @@ -13,7 +13,7 @@ use crate::{ pub(crate) struct DocumentItem { pub(crate) span: TokenSpan, pub(crate) import_group: Option, - pub(crate) compact_group: Option, + pub(crate) compact_group: Option, pub(crate) leading: Trivia, pub(crate) trailing: Trivia, pub(crate) identity_key: String, @@ -21,6 +21,13 @@ pub(crate) struct DocumentItem { original_index: usize, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum CompactGroup { + Constant, + TypeAlias, + ExternDeclaration, +} + #[derive(Debug)] pub(crate) struct DocumentPlan { pub(crate) header: Trivia, @@ -68,12 +75,22 @@ pub(crate) fn plan_document( }); previous_was_import = is_import; - let compact_group = matches!( - item.syntax().kind(), - SyntaxKind::ConstantItem | SyntaxKind::TypeAliasItem - ) - .then_some(item.syntax().kind()) - .filter(|_| !item.syntax().text().to_string().contains('\n')); + let compact_group = match item.syntax().kind() { + SyntaxKind::ConstantItem if !item.syntax().text().to_string().contains('\n') => { + Some(CompactGroup::Constant) + } + SyntaxKind::TypeAliasItem if !item.syntax().text().to_string().contains('\n') => { + Some(CompactGroup::TypeAlias) + } + SyntaxKind::FunctionItem + if AstFunctionItem::cast(item.syntax().clone()).is_some_and(|function| { + function.extern_kw().is_some() && function.body().is_none() + }) => + { + Some(CompactGroup::ExternDeclaration) + } + _ => None, + }; let path_key = stream .tokens_in(span) .iter() diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index 1d0ebe0d..0601f812 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -517,6 +517,23 @@ fn item_modifiers_modules_and_extern_functions() { ); } +#[test] +fn bodyless_extern_functions_can_form_compact_groups() { + check( + "extern fn first()->Int from \"./first.hex\";\nextern fn second()->Int from \"./second.hex\";\n\nextern fn third()->Int from \"./third.hex\";\nextern fn with_body()->Int{1}", + expect![[r#" + extern fn first() -> Int from "./first.hex"; + extern fn second() -> Int from "./second.hex"; + + extern fn third() -> Int from "./third.hex"; + + extern fn with_body() -> Int { + 1 + } + "#]], + ); +} + #[test] fn struct_field_forms() { check( diff --git a/tests/imports/import_order.rue b/tests/imports/import_order.rue index a939f5f9..1ee0c1e6 100644 --- a/tests/imports/import_order.rue +++ b/tests/imports/import_order.rue @@ -1,5 +1,4 @@ import a::hello_world_a; - import b::hello_world_b; mod a { diff --git a/tests/imports/unresolved_imports.rue b/tests/imports/unresolved_imports.rue index c441bd1e..c4a5396d 100644 --- a/tests/imports/unresolved_imports.rue +++ b/tests/imports/unresolved_imports.rue @@ -1,7 +1,5 @@ -import module::missing; - -import module::inner::missing; - import module::inner::*; +import module::inner::missing; +import module::missing; mod module {} diff --git a/tests/imports/unresolved_imports.yaml b/tests/imports/unresolved_imports.yaml index 6cfcccf1..ebf20f46 100644 --- a/tests/imports/unresolved_imports.yaml +++ b/tests/imports/unresolved_imports.yaml @@ -1,4 +1,4 @@ diagnostics: -- Undeclared symbol `inner` at unresolved_imports.rue:3:16 -- Undeclared symbol `inner` at unresolved_imports.rue:5:16 -- Unresolved import `missing` at unresolved_imports.rue:1:16 +- Undeclared symbol `inner` at unresolved_imports.rue:1:16 +- Undeclared symbol `inner` at unresolved_imports.rue:2:16 +- Unresolved import `missing` at unresolved_imports.rue:3:16 diff --git a/tests/projects/external_import/main.rue b/tests/projects/external_import/main.rue index 9256dac3..a6445b58 100644 --- a/tests/projects/external_import/main.rue +++ b/tests/projects/external_import/main.rue @@ -1,7 +1,6 @@ import nested::helper::add_nested; extern fn add(a: Int, b: Int) -> Int from "./add.hex"; - extern fn add_again(a: Int, b: Int) -> Int from "./add.hex"; test fn call_external() -> Int { diff --git a/tests/symbols/closures.rue b/tests/symbols/closures.rue index 5fca6212..44723615 100644 --- a/tests/symbols/closures.rue +++ b/tests/symbols/closures.rue @@ -65,7 +65,6 @@ fn create_adder(a: Int) -> fn(b: Int) -> Int { } const CAPTURE_1: Int = 42; - const CAPTURE_2: Int = 34; fn tree_params(a: Int, b: Int, c: Int, d: Int) -> Int { diff --git a/tests/symbols/closures.yaml b/tests/symbols/closures.yaml index 8cd0299f..573f91e2 100644 --- a/tests/symbols/closures.yaml +++ b/tests/symbols/closures.yaml @@ -67,7 +67,7 @@ tests: total_cost: 711770 - name: closure_sequential_captures program: (a (q 2 (i (= (a (c (q . 2) (c (c (q . 1) (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 95 (q . 4)) (q 16 5 2) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 6) (c (q . 1) ()))) ()))) ()))) (q 1 2 3 4)) (q . 76)) (q 2 (i (= (a (c (q . 2) (c (c (q . 1) (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 63 (q . 4)) (q 16 5 2) (q 8)) 1) (q 8)) 1) (q 8)) 1) (q 8)) 1)) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 6) (c (q . 1) ()))) ()))) ()))) (q 1 2 3 . 4)) (q . 76)) (q) (q 8)) 1) (q 8)) 1) (q (all . 42))) - debug_program: (a (q 2 (q 2 (q 2 (i (= 4 (q . 76)) (q 2 (i (= 6 (q . 76)) (q) (q 8 (q . "assertion failed at closures.rue:60:5"))) 1) (q 8 (q . "assertion failed at closures.rue:59:5"))) 1) (c (c (a 4 (c (q . 1) (c (q . 2) (c (q . 3) (c (q . 4) ()))))) (a 6 (c (q . 1) (c (q . 2) (c (q . 3) (q . 4)))))) 1)) (c (c (c (q . 2) (c (c (q . 1) 30) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 10) (c (q . 1) ()))) ()))) ()))) (c (q . 2) (c (c (q . 1) 22) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 10) (c (q . 1) ()))) ()))) ())))) 1)) (c (c (q . 34) (c (q . 42) (c (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 63 (q . 4)) (q 16 5 2) (q 8 (q . "assertion failed at closures.rue:83:5"))) 1) (q 8 (q . "assertion failed at closures.rue:82:5"))) 1) (q 8 (q . "assertion failed at closures.rue:81:5"))) 1) (q 8 (q . "assertion failed at closures.rue:80:5"))) 1) (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 95 (q . 4)) (q 16 5 2) (q 8 (q . "assertion failed at closures.rue:75:5"))) 1) (q 8 (q . "assertion failed at closures.rue:74:5"))) 1) (q 8 (q . "assertion failed at closures.rue:73:5"))) 1) (q 8 (q . "assertion failed at closures.rue:72:5"))) 1)))) ())) + debug_program: (a (q 2 (q 2 (q 2 (i (= 4 (q . 76)) (q 2 (i (= 6 (q . 76)) (q) (q 8 (q . "assertion failed at closures.rue:60:5"))) 1) (q 8 (q . "assertion failed at closures.rue:59:5"))) 1) (c (c (a 4 (c (q . 1) (c (q . 2) (c (q . 3) (c (q . 4) ()))))) (a 6 (c (q . 1) (c (q . 2) (c (q . 3) (q . 4)))))) 1)) (c (c (c (q . 2) (c (c (q . 1) 30) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 10) (c (q . 1) ()))) ()))) ()))) (c (q . 2) (c (c (q . 1) 22) (c (c (q . 4) (c (c (q . 1) 4) (c (c (q . 4) (c (c (q . 1) 10) (c (q . 1) ()))) ()))) ())))) 1)) (c (c (q . 34) (c (q . 42) (c (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 63 (q . 4)) (q 16 5 2) (q 8 (q . "assertion failed at closures.rue:82:5"))) 1) (q 8 (q . "assertion failed at closures.rue:81:5"))) 1) (q 8 (q . "assertion failed at closures.rue:80:5"))) 1) (q 8 (q . "assertion failed at closures.rue:79:5"))) 1) (q 2 (i (= 11 (q . 1)) (q 2 (i (= 23 (q . 2)) (q 2 (i (= 47 (q . 3)) (q 2 (i (= 95 (q . 4)) (q 16 5 2) (q 8 (q . "assertion failed at closures.rue:74:5"))) 1) (q 8 (q . "assertion failed at closures.rue:73:5"))) 1) (q 8 (q . "assertion failed at closures.rue:72:5"))) 1) (q 8 (q . "assertion failed at closures.rue:71:5"))) 1)))) ())) output: () runtime_cost: 8701 byte_cost: 6156000 diff --git a/tests/symbols/complex_constants.rue b/tests/symbols/complex_constants.rue index 081abf89..3097cd6f 100644 --- a/tests/symbols/complex_constants.rue +++ b/tests/symbols/complex_constants.rue @@ -1,9 +1,6 @@ const HELLO: String = "Hello"; - const WORLD: String = "world"; - const GREETING: String = greet(WORLD); - const MESSAGE: String = GREETING + " How are you doing?"; fn main() -> String { diff --git a/tests/symbols/invalid_recursion.rue b/tests/symbols/invalid_recursion.rue index 4990569f..7b5bac2b 100644 --- a/tests/symbols/invalid_recursion.rue +++ b/tests/symbols/invalid_recursion.rue @@ -11,9 +11,6 @@ inline fn factorial(num: Int) -> Int { } const A: Int = A; - const B: Int = C; - const C: Int = B; - inline const D: Int = D; diff --git a/tests/symbols/invalid_recursion.yaml b/tests/symbols/invalid_recursion.yaml index 3d51d71b..b486c56a 100644 --- a/tests/symbols/invalid_recursion.yaml +++ b/tests/symbols/invalid_recursion.yaml @@ -1,6 +1,6 @@ diagnostics: - Function `factorial` cannot be inline, since it references itself at invalid_recursion.rue:5:11 - Constant `A` references itself at invalid_recursion.rue:13:7 -- Constant `B` references itself at invalid_recursion.rue:15:7 -- Constant `C` references itself at invalid_recursion.rue:17:7 -- Constant `D` references itself at invalid_recursion.rue:19:14 +- Constant `B` references itself at invalid_recursion.rue:14:7 +- Constant `C` references itself at invalid_recursion.rue:15:7 +- Constant `D` references itself at invalid_recursion.rue:16:14 diff --git a/tests/symbols/super_errors.rue b/tests/symbols/super_errors.rue index 834b8d13..2da45d93 100644 --- a/tests/symbols/super_errors.rue +++ b/tests/symbols/super_errors.rue @@ -1,8 +1,6 @@ -import super::x; - import super; - import super::super; +import super::x; mod a { import super::a; diff --git a/tests/symbols/super_errors.yaml b/tests/symbols/super_errors.yaml index 72c49a97..cffc4db5 100644 --- a/tests/symbols/super_errors.yaml +++ b/tests/symbols/super_errors.yaml @@ -1,10 +1,10 @@ diagnostics: -- Unresolved `super`, there is no parent module at super_errors.rue:1:8 -- Cannot end path in `super` at super_errors.rue:3:8 -- Unresolved `super`, there is no parent module at super_errors.rue:5:8 -- Cannot end path in `super` at super_errors.rue:5:15 -- Unresolved `super`, there is no parent module at super_errors.rue:8:12 -- Unresolved import `x` at super_errors.rue:1:15 -- Unresolved import `super` at super_errors.rue:3:8 -- Unresolved import `super` at super_errors.rue:5:15 -- Unused import `a` at super_errors.rue:8:19 +- Cannot end path in `super` at super_errors.rue:1:8 +- Unresolved `super`, there is no parent module at super_errors.rue:2:8 +- Cannot end path in `super` at super_errors.rue:2:15 +- Unresolved `super`, there is no parent module at super_errors.rue:3:8 +- Unresolved `super`, there is no parent module at super_errors.rue:6:12 +- Unresolved import `super` at super_errors.rue:1:8 +- Unresolved import `super` at super_errors.rue:2:15 +- Unresolved import `x` at super_errors.rue:3:15 +- Unused import `a` at super_errors.rue:6:19 diff --git a/tests/warnings/unused_imports.rue b/tests/warnings/unused_imports.rue index e44e272a..1d1739c2 100644 --- a/tests/warnings/unused_imports.rue +++ b/tests/warnings/unused_imports.rue @@ -1,24 +1,14 @@ -import single::a; - +import deeply::nested; +import empty::*; import glob::*; - -import shadow_1::c; - +import module::used; +import nested::module; +import private::*; import shadow_1::*; - +import shadow_1::c; import shadow_2::*; - import shadow_2::d; - -import private::*; - -import empty::*; - -import deeply::nested; - -import nested::module; - -import module::used; +import single::a; mod single { export fn a() -> Int { diff --git a/tests/warnings/unused_imports.yaml b/tests/warnings/unused_imports.yaml index a974ad3d..e9cde546 100644 --- a/tests/warnings/unused_imports.yaml +++ b/tests/warnings/unused_imports.yaml @@ -5,16 +5,16 @@ runtime_cost: 20 byte_cost: 36000 total_cost: 36020 diagnostics: -- Unused import `d` at unused_imports.rue:11:18 -- Unused import `*` at unused_imports.rue:7:18 -- Unused import `*` at unused_imports.rue:13:17 -- Unused import `*` at unused_imports.rue:15:15 -- Unused import `a` at unused_imports.rue:1:16 +- Unused import `c` at unused_imports.rue:8:18 +- Unused import `d` at unused_imports.rue:10:18 +- Unused import `*` at unused_imports.rue:2:15 +- Unused import `*` at unused_imports.rue:6:17 - Unused import `*` at unused_imports.rue:3:14 -- Unused import `c` at unused_imports.rue:5:18 +- Unused import `*` at unused_imports.rue:7:18 - Unused import `*` at unused_imports.rue:9:18 -- Unused function `a` at unused_imports.rue:24:15 -- Unused function `b` at unused_imports.rue:30:15 -- Unused function `c` at unused_imports.rue:36:15 -- Unused function `d` at unused_imports.rue:42:15 -- Unused function `private` at unused_imports.rue:48:8 +- Unused import `a` at unused_imports.rue:11:16 +- Unused function `a` at unused_imports.rue:14:15 +- Unused function `b` at unused_imports.rue:20:15 +- Unused function `c` at unused_imports.rue:26:15 +- Unused function `d` at unused_imports.rue:32:15 +- Unused function `private` at unused_imports.rue:38:8 diff --git a/tests/warnings/unused_symbols.rue b/tests/warnings/unused_symbols.rue index 7df71a90..91c98c36 100644 --- a/tests/warnings/unused_symbols.rue +++ b/tests/warnings/unused_symbols.rue @@ -24,9 +24,7 @@ test fn tests() -> Int { } const USED_BY_MAIN: Int = 42; - const USED_BY_TESTS: Int = 34; - const USED_BY_BOTH: Int = 69; const UNUSED: Int = 0; diff --git a/tests/warnings/unused_symbols.yaml b/tests/warnings/unused_symbols.yaml index 4c8435d8..b97de45f 100644 --- a/tests/warnings/unused_symbols.yaml +++ b/tests/warnings/unused_symbols.yaml @@ -14,7 +14,7 @@ tests: total_cost: 132142 diagnostics: - Unused constant `UNUSED_IN_MODULE` at unused_symbols.rue:3:18 -- Unused constant `UNUSED` at unused_symbols.rue:32:7 -- Unused function `unused` at unused_symbols.rue:40:4 +- Unused constant `UNUSED` at unused_symbols.rue:30:7 +- Unused function `unused` at unused_symbols.rue:38:4 - Unused binding `unused` at unused_symbols.rue:12:9 - Unused binding `unused` at unused_symbols.rue:20:9 diff --git a/tests/warnings/unused_types.rue b/tests/warnings/unused_types.rue index c4cbc30a..7b323822 100644 --- a/tests/warnings/unused_types.rue +++ b/tests/warnings/unused_types.rue @@ -1,15 +1,10 @@ type Recursive = Recursive; - type A = B; - type B = A; - type Used = 42; - type Unused = "Hello, world!"; const USED_VALUE: 42 = 42; - const UNUSED_VALUE: 34 = 34; struct UsedStruct { @@ -21,15 +16,11 @@ struct UnusedStruct { } type UsedAlias = UsedStruct; - type UnusedAlias = UnusedStruct; - type Thing1 = 42; - type Thing2 = 42; const INNER: Thing1 = 42; - const OUTER: Thing2 = 42; struct Inner { diff --git a/tests/warnings/unused_types.yaml b/tests/warnings/unused_types.yaml index 53e39dd4..544e4701 100644 --- a/tests/warnings/unused_types.yaml +++ b/tests/warnings/unused_types.yaml @@ -27,8 +27,8 @@ tests: byte_cost: 12000 total_cost: 12044 diagnostics: -- Unused constant `UNUSED_VALUE` at unused_types.rue:13:7 +- Unused constant `UNUSED_VALUE` at unused_types.rue:8:7 - Unused type alias `Recursive` at unused_types.rue:1:6 -- Unused type alias `Unused` at unused_types.rue:9:6 -- Unused struct `UnusedStruct` at unused_types.rue:19:8 -- Unused type alias `UnusedAlias` at unused_types.rue:25:6 +- Unused type alias `Unused` at unused_types.rue:5:6 +- Unused struct `UnusedStruct` at unused_types.rue:14:8 +- Unused type alias `UnusedAlias` at unused_types.rue:19:6 From 60323bb9b41877067c2dff10486e814e55e13b49 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 17:46:58 -0400 Subject: [PATCH 15/21] Add back newline --- crates/rue-compiler/src/std/merkle.rue | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rue-compiler/src/std/merkle.rue b/crates/rue-compiler/src/std/merkle.rue index 07f99321..83580808 100644 --- a/crates/rue-compiler/src/std/merkle.rue +++ b/crates/rue-compiler/src/std/merkle.rue @@ -1,4 +1,5 @@ // merkle.rue by yakuhito + import tree_hash::{tree_hash_atom, tree_hash_pair}; export struct MerkleProof { From b5bb200c2b710cb336af81f5911426588ef560d2 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 17:53:10 -0400 Subject: [PATCH 16/21] Condense if expr when possible --- crates/rue-compiler/src/std/merkle.rue | 24 +++---------- crates/rue-formatter/src/analysis.rs | 49 ++++++++++++++++++++++---- crates/rue-formatter/src/emit.rs | 26 +++++++++----- crates/rue-formatter/src/tests.rs | 17 +++++++++ examples/puzzles/royalty_split.rue | 6 +--- tests/expressions/if_else.rue | 14 ++------ 6 files changed, 84 insertions(+), 52 deletions(-) diff --git a/crates/rue-compiler/src/std/merkle.rue b/crates/rue-compiler/src/std/merkle.rue index 83580808..9aefcfc4 100644 --- a/crates/rue-compiler/src/std/merkle.rue +++ b/crates/rue-compiler/src/std/merkle.rue @@ -19,16 +19,8 @@ export fn calculate_merkle_root( calculate_merkle_root( path >>> 1, tree_hash_pair( - inline if path & 1 != 0 { - additional_steps.first - } else { - current_hash - }, - inline if path & 1 != 0 { - current_hash - } else { - additional_steps.first - }, + inline if path & 1 != 0 { additional_steps.first } else { current_hash }, + inline if path & 1 != 0 { current_hash } else { additional_steps.first }, ), additional_steps.rest, ) @@ -41,16 +33,8 @@ fn simplify_merkle_proof_after_leaf(leaf_hash: Bytes32, proof: MerkleProof) -> B simplify_merkle_proof_after_leaf( tree_hash_pair( - inline if proof.path & 1 != 0 { - proof.hashes.first - } else { - leaf_hash - }, - inline if proof.path & 1 != 0 { - leaf_hash - } else { - proof.hashes.first - }, + inline if proof.path & 1 != 0 { proof.hashes.first } else { leaf_hash }, + inline if proof.path & 1 != 0 { leaf_hash } else { proof.hashes.first }, ), MerkleProof { path: proof.path >>> 1, hashes: proof.hashes.rest }, ) diff --git a/crates/rue-formatter/src/analysis.rs b/crates/rue-formatter/src/analysis.rs index 5ff2be2d..455d4d30 100644 --- a/crates/rue-formatter/src/analysis.rs +++ b/crates/rue-formatter/src/analysis.rs @@ -11,6 +11,7 @@ use crate::{ pub(crate) enum DelimiterStyle { Block, Braced, + ConditionalBraced, Group, Fill, FillBraced, @@ -56,6 +57,7 @@ impl TokenFlags { const PREFIX_OPERATOR: u8 = 1 << 2; const ATTACHED_OPENER: u8 = 1 << 3; const ABSOLUTE_PATH_START: u8 = 1 << 4; + const CONDITIONAL_GROUP: u8 = 1 << 5; fn insert(&mut self, flag: u8) { self.0 |= flag; @@ -86,6 +88,10 @@ impl TokenFacts { pub(crate) fn is_absolute_path_start(&self) -> bool { self.flags.contains(TokenFlags::ABSOLUTE_PATH_START) } + + pub(crate) fn is_conditional_group(&self) -> bool { + self.flags.contains(TokenFlags::CONDITIONAL_GROUP) + } } #[derive(Debug)] @@ -299,13 +305,29 @@ fn analyze_nodes( | AstNodeKind::ConstExpr | AstNodeKind::ListItem | AstNodeKind::BinaryExpr - | AstNodeKind::IfExpr | AstNodeKind::GuardExpr | AstNodeKind::CastExpr | AstNodeKind::FieldAccessExpr | AstNodeKind::NamedBinding | AstNodeKind::ListBindingItem | AstNodeKind::StructFieldBinding => {} + AstNodeKind::IfExpr => { + if !is_inline_conditional(&node) { + continue; + } + let mut tokens = significant_tokens(&node); + let first = tokens.next().ok_or_else(|| { + FormatError::Internal( + "conditional expression has no significant token".to_string(), + ) + })?; + let last = tokens.last().unwrap_or_else(|| first.clone()); + let start = token_id(&first, stream)?; + facts[start.index()].group_end = Some(token_id(&last, stream)?); + facts[start.index()] + .flags + .insert(TokenFlags::CONDITIONAL_GROUP); + } } } Ok(()) @@ -363,11 +385,8 @@ fn expression_block_style( || node.parent().is_some_and(|parent| { matches!( parent.kind(), - SyntaxKind::FunctionItem - | SyntaxKind::ModuleItem - | SyntaxKind::IfExpr - | SyntaxKind::IfStmt - ) + SyntaxKind::FunctionItem | SyntaxKind::ModuleItem | SyntaxKind::IfStmt + ) || (parent.kind() == SyntaxKind::IfExpr && !is_inline_conditional(&parent)) }) { return None; @@ -379,6 +398,12 @@ fn expression_block_style( return None; } let kind = AstNodeKind::of(expression.syntax())?; + if node + .parent() + .is_some_and(|parent| parent.kind() == SyntaxKind::IfExpr) + { + return Some(DelimiterStyle::ConditionalBraced); + } Some(if is_transparent_expression_wrapper(kind) { DelimiterStyle::FillBraced } else { @@ -386,6 +411,18 @@ fn expression_block_style( }) } +fn is_inline_conditional(node: &SyntaxNode) -> bool { + node.ancestors() + .take_while(|ancestor| ancestor.kind() == SyntaxKind::IfExpr) + .any(|conditional| { + conditional.children_with_tokens().any(|element| { + element + .as_token() + .is_some_and(|token| token.kind() == T![inline]) + }) + }) +} + fn delimiter_contains_comments(open: TokenId, stream: &TokenStream, facts: &[TokenFacts]) -> bool { let Some(close) = facts[open.index()].pair else { return false; diff --git a/crates/rue-formatter/src/emit.rs b/crates/rue-formatter/src/emit.rs index be002a8e..6bb87609 100644 --- a/crates/rue-formatter/src/emit.rs +++ b/crates/rue-formatter/src/emit.rs @@ -111,6 +111,9 @@ impl<'a> Formatter<'a> { { return Ok(self.binary_span(span)?.group()); } + if allow_outer_group && self.layout.facts(span.start()).is_conditional_group() { + return Ok(self.span_inner(span, false)?.group()); + } if allow_outer_group { return self.grouped_span(span); } @@ -188,6 +191,9 @@ impl<'a> Formatter<'a> { } fn grouped_span(&mut self, span: TokenSpan) -> Result { + if self.layout.facts(span.start()).is_conditional_group() { + return Ok(self.span_inner(span, false)?.group()); + } if !self .layout .facts(span.start()) @@ -319,7 +325,7 @@ impl<'a> Formatter<'a> { leading_gap, match style { DelimiterStyle::Block => Separator::Hard, - DelimiterStyle::Braced => Separator::Soft, + DelimiterStyle::Braced | DelimiterStyle::ConditionalBraced => Separator::Soft, DelimiterStyle::Group | DelimiterStyle::Fill | DelimiterStyle::FillBraced @@ -333,7 +339,7 @@ impl<'a> Formatter<'a> { trailing_gap, match style { DelimiterStyle::Block => Separator::Hard, - DelimiterStyle::Braced => Separator::Soft, + DelimiterStyle::Braced | DelimiterStyle::ConditionalBraced => Separator::Soft, DelimiterStyle::Group | DelimiterStyle::Fill | DelimiterStyle::FillBraced @@ -343,13 +349,15 @@ impl<'a> Formatter<'a> { ); Ok(match style { - DelimiterStyle::Block | DelimiterStyle::Braced => Doc::concat([ - open_doc, - Doc::concat([leading, inner, comma]).indent(), - trailing, - close_doc, - ]) - .group_if(style == DelimiterStyle::Braced), + DelimiterStyle::Block | DelimiterStyle::Braced | DelimiterStyle::ConditionalBraced => { + Doc::concat([ + open_doc, + Doc::concat([leading, inner, comma]).indent(), + trailing, + close_doc, + ]) + .group_if(style == DelimiterStyle::Braced) + } DelimiterStyle::Fill | DelimiterStyle::FillBraced | DelimiterStyle::Hug => { let space_inside = style == DelimiterStyle::FillBraced; let closing_space = if space_inside { Doc::space() } else { Doc::Nil }; diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index 0601f812..28f9e164 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -976,6 +976,23 @@ fn lambda_and_conditional_expression_forms() { ); } +#[test] +fn short_conditional_expression_blocks_stay_flat() { + let source = "fn main(){let value=inline if condition{first}else{second};}"; + let exact = FormatOptions { + max_width: 62, + ..FormatOptions::default() + }; + let flat = format_source(source, &exact).unwrap(); + expect![[r#" + fn main() { + let value = inline if condition { first } else { second }; + } + "#]] + .assert_eq(&flat); + assert_eq!(format_source(&flat, &exact).unwrap(), flat); +} + #[test] fn type_forms() { check( diff --git a/examples/puzzles/royalty_split.rue b/examples/puzzles/royalty_split.rue index 257993d3..d91fcc45 100644 --- a/examples/puzzles/royalty_split.rue +++ b/examples/puzzles/royalty_split.rue @@ -52,11 +52,7 @@ fn split_amount_and_create_coins( ) -> List { let create_coin = CreateCoin { puzzle_hash: payouts.first.puzzle_hash, - amount: inline if payouts.rest is nil { - remaining_amount - } else { - this_amount - }, + amount: inline if payouts.rest is nil { remaining_amount } else { this_amount }, memos: Memos { value: [payouts.first.puzzle_hash] }, }; diff --git a/tests/expressions/if_else.rue b/tests/expressions/if_else.rue index c1d5dcee..253e2180 100644 --- a/tests/expressions/if_else.rue +++ b/tests/expressions/if_else.rue @@ -7,19 +7,9 @@ test fn lazy_if(value: Int) -> String { } test fn eager_if_grouped(value: Int) -> String { - ( - inline if value > 100 { - "Large" - } else { - "Small" - } - ) + (inline if value > 100 { "Large" } else { "Small" }) } test fn eager_if_ungrouped(value: Int) -> String { - inline if value > 100 { - "Large" - } else { - "Small" - } + inline if value > 100 { "Large" } else { "Small" } } From 01158cbdefb0fb2fde59b3a1f55b7f50e3759a8e Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 18:07:42 -0400 Subject: [PATCH 17/21] Fix import order --- crates/rue-compiler/src/compile/imports.rs | 49 +++++++++++++- tests/imports/import_order.rue | 77 ++++++++++++++++++++++ tests/imports/import_order.yaml | 35 ++++++++++ tests/imports/unresolved_imports.yaml | 2 +- tests/warnings/unused_imports.yaml | 6 +- 5 files changed, 163 insertions(+), 6 deletions(-) diff --git a/crates/rue-compiler/src/compile/imports.rs b/crates/rue-compiler/src/compile/imports.rs index 2b85045b..52fc500c 100644 --- a/crates/rue-compiler/src/compile/imports.rs +++ b/crates/rue-compiler/src/compile/imports.rs @@ -1,4 +1,7 @@ -use std::collections::HashMap; +use std::{ + cmp::Ordering, + collections::{HashMap, HashSet}, +}; use indexmap::IndexMap; use rowan::TextRange; @@ -166,7 +169,15 @@ pub fn resolve_imports( cache: &mut ImportCache, diagnostics: bool, ) { - let imports = flatten_imports(ctx, all_modules); + let mut imports = flatten_imports(ctx, all_modules); + imports.sort_by(|(_, left), (_, right)| compare_imports(ctx.import(*left), ctx.import(*right))); + let targeted_names = imports + .iter() + .filter_map(|(scope, import)| match &ctx.import(*import).items { + Items::Named(name) => Some((*scope, name.text().to_string())), + Items::All(_) => None, + }) + .collect::>(); let mut updated = true; let mut missing_imports = IndexMap::new(); @@ -182,6 +193,7 @@ pub fn resolve_imports( diagnostics, cache, &mut missing_imports, + &targeted_names, ); } } @@ -210,6 +222,32 @@ pub fn resolve_imports( } } +fn compare_imports(left: &Import, right: &Import) -> Ordering { + import_priority(&left.items) + .cmp(&import_priority(&right.items)) + .then_with(|| { + left.path + .iter() + .map(Name::text) + .cmp(right.path.iter().map(Name::text)) + }) + .then_with(|| import_item_name(&left.items).cmp(import_item_name(&right.items))) + .then_with(|| left.exported.cmp(&right.exported)) +} + +fn import_priority(items: &Items) -> u8 { + match items { + Items::Named(_) => 0, + Items::All(_) => 1, + } +} + +fn import_item_name(items: &Items) -> &str { + match items { + Items::Named(name) | Items::All(name) => name.text(), + } +} + fn resolve_import( ctx: &mut Compiler, import_scope: ScopeId, @@ -217,6 +255,7 @@ fn resolve_import( diagnostics: bool, cache: &mut ImportCache, missing_imports: &mut IndexMap>, + targeted_names: &HashSet<(ScopeId, String)>, ) -> bool { let import = ctx.import(import_id).clone(); let has_super = import.has_super; @@ -351,6 +390,9 @@ fn resolve_import( }; for (name, symbol) in symbols { + if targeted_names.contains(&(import_scope, name.clone())) { + continue; + } let target = ctx.scope_mut(import_scope); if target.symbol(&name).is_none() { @@ -375,6 +417,9 @@ fn resolve_import( } for (name, ty) in types { + if targeted_names.contains(&(import_scope, name.clone())) { + continue; + } let target = ctx.scope_mut(import_scope); if target.ty(&name).is_none() { diff --git a/tests/imports/import_order.rue b/tests/imports/import_order.rue index 1ee0c1e6..2fefa35d 100644 --- a/tests/imports/import_order.rue +++ b/tests/imports/import_order.rue @@ -25,6 +25,63 @@ mod b { } } +mod targeted { + export const UNIQUE_TARGETED: Int = 30; + export const VALUE: Int = 1; + export type Choice = Int; +} + +mod globbed { + export const GLOB_ONLY: Int = 40; + export const VALUE: Int = 2; + export type Choice = String; + + export fn consume(value: Choice) -> Choice { + assert VALUE > 0; + value + } +} + +mod glob_order_one { + import targeted::*; + + import globbed::*; + + export fn result() -> Int { + GLOB_ONLY + UNIQUE_TARGETED + VALUE + } +} + +mod glob_order_two { + import globbed::*; + + import targeted::*; + + export fn result() -> Int { + GLOB_ONLY + UNIQUE_TARGETED + VALUE + } +} + +mod glob_first { + import globbed::*; + + import targeted::{Choice, VALUE}; + + export fn result(value: Choice) -> Int { + GLOB_ONLY + VALUE + value + } +} + +mod targeted_first { + import targeted::{Choice, VALUE}; + + import globbed::*; + + export fn result(value: Choice) -> Int { + GLOB_ONLY + VALUE + value + } +} + test fn a_entrypoint() -> String { hello_world_a() } @@ -32,3 +89,23 @@ test fn a_entrypoint() -> String { test fn b_entrypoint() -> String { hello_world_b() } + +test fn targeted_import_wins_after_glob() -> Int { + glob_first::result(1) +} + +test fn targeted_import_wins_before_glob() -> Int { + targeted_first::result(1) +} + +test fn glob_collisions_are_canonical_in_first_order() -> Int { + glob_order_one::result() +} + +test fn glob_collisions_are_canonical_in_second_order() -> Int { + glob_order_two::result() +} + +test fn globbed_declarations_are_used() -> String { + globbed::consume("used") +} diff --git a/tests/imports/import_order.yaml b/tests/imports/import_order.yaml index 7a555d98..f79481c7 100644 --- a/tests/imports/import_order.yaml +++ b/tests/imports/import_order.yaml @@ -13,3 +13,38 @@ tests: runtime_cost: 20 byte_cost: 192000 total_cost: 192020 +- name: targeted_import_wins_after_glob + program: (q . 42) + debug_program: (a (q 2 4 (c (c 10 14) (q . 1))) (c (c (q 16 (+ 6 4) 3) (c (q . 1) (q . 40))) ())) + output: '42' + runtime_cost: 20 + byte_cost: 36000 + total_cost: 36020 +- name: targeted_import_wins_before_glob + program: (q . 42) + debug_program: (a (q 2 4 (c (c 10 14) (q . 1))) (c (c (q 16 (+ 6 4) 3) (c (q . 1) (q . 40))) ())) + output: '42' + runtime_cost: 20 + byte_cost: 36000 + total_cost: 36020 +- name: glob_collisions_are_canonical_in_first_order + program: (q . 72) + debug_program: (a (q 2 8 (c (c 12 (c 10 14)) ())) (c (c (c (q 16 (+ 14 10) 4) (q . 2)) (c (q . 30) (q . 40))) ())) + output: '72' + runtime_cost: 20 + byte_cost: 36000 + total_cost: 36020 +- name: glob_collisions_are_canonical_in_second_order + program: (q . 72) + debug_program: (a (q 2 8 (c (c 12 (c 10 14)) ())) (c (c (c (q 16 (+ 14 10) 4) (q . 2)) (c (q . 30) (q . 40))) ())) + output: '72' + runtime_cost: 20 + byte_cost: 36000 + total_cost: 36020 +- name: globbed_declarations_are_used + program: (a (i (> (q . 2) ()) (q 1 . "used") (q 8)) 1) + debug_program: (a (q 2 4 (c 6 (q . "used"))) (c (c (q 2 (i (> 2 ()) (q . 3) (q 8 (q . "assertion failed at import_order.rue:40:9"))) 1) (q . 2)) ())) + output: '"used"' + runtime_cost: 794 + byte_cost: 420000 + total_cost: 420794 diff --git a/tests/imports/unresolved_imports.yaml b/tests/imports/unresolved_imports.yaml index ebf20f46..162566fc 100644 --- a/tests/imports/unresolved_imports.yaml +++ b/tests/imports/unresolved_imports.yaml @@ -1,4 +1,4 @@ diagnostics: -- Undeclared symbol `inner` at unresolved_imports.rue:1:16 - Undeclared symbol `inner` at unresolved_imports.rue:2:16 +- Undeclared symbol `inner` at unresolved_imports.rue:1:16 - Unresolved import `missing` at unresolved_imports.rue:3:16 diff --git a/tests/warnings/unused_imports.yaml b/tests/warnings/unused_imports.yaml index e9cde546..754d0139 100644 --- a/tests/warnings/unused_imports.yaml +++ b/tests/warnings/unused_imports.yaml @@ -5,13 +5,13 @@ runtime_cost: 20 byte_cost: 36000 total_cost: 36020 diagnostics: -- Unused import `c` at unused_imports.rue:8:18 -- Unused import `d` at unused_imports.rue:10:18 - Unused import `*` at unused_imports.rue:2:15 - Unused import `*` at unused_imports.rue:6:17 -- Unused import `*` at unused_imports.rue:3:14 - Unused import `*` at unused_imports.rue:7:18 - Unused import `*` at unused_imports.rue:9:18 +- Unused import `*` at unused_imports.rue:3:14 +- Unused import `c` at unused_imports.rue:8:18 +- Unused import `d` at unused_imports.rue:10:18 - Unused import `a` at unused_imports.rue:11:16 - Unused function `a` at unused_imports.rue:14:15 - Unused function `b` at unused_imports.rue:20:15 From 579a2ff0d5ae6baf6a7f07368d4a093acb01138d Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 18:27:00 -0400 Subject: [PATCH 18/21] Add Rue fmt CLI command --- .github/workflows/build.yml | 3 + Cargo.lock | 35 +++++++- Cargo.toml | 2 + crates/rue-cli/Cargo.toml | 3 + crates/rue-cli/src/fmt.rs | 152 +++++++++++++++++++++++++++++++++ crates/rue-cli/src/main.rs | 6 ++ crates/rue-lir/src/optimize.rs | 2 - crates/rue-parser/src/lib.rs | 1 - 8 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 crates/rue-cli/src/fmt.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b629d691..9f4d81ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,9 @@ jobs: - name: Fmt run: cargo fmt --all -- --files-with-diff --check + - name: Rue fmt + run: cargo run --quiet --package rue-cli -- fmt 'examples/**/*.rue' 'tests/**/*.rue' 'crates/rue-compiler/src/std/**/*.rue' --check + - name: Publish (dry run) run: | cargo binstall cargo-workspaces diff --git a/Cargo.lock b/Cargo.lock index 44fd082e..4a4b060c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -845,9 +845,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "group" @@ -1729,12 +1729,15 @@ dependencies = [ "clvm-utils", "clvmr", "colored", + "glob", "hex", "rue-compiler", "rue-diagnostic", + "rue-formatter", "rue-lir", "rue-options", "toml", + "walkdir", ] [[package]] @@ -1955,6 +1958,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2539,6 +2551,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2615,6 +2637,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "windows-link" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index 7ce2fb2d..00e0e236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ match_same_arms = "allow" similar_names = "allow" cast_possible_truncation = "allow" case_sensitive_file_extension_comparisons = "allow" +wildcard_imports = "allow" [workspace.dependencies] rue-lexer = { path = "crates/rue-lexer", version = "0.9.0" } @@ -76,6 +77,7 @@ clap = "4.5.45" rstest = "0.26.1" log = "0.4.27" env_logger = "0.11.8" +glob = "0.3.4" wasm-bindgen = "0.2.102" console_error_panic_hook = "0.1.7" sha2 = "0.10.9" diff --git a/crates/rue-cli/Cargo.toml b/crates/rue-cli/Cargo.toml index 808e9811..8f67d7d6 100644 --- a/crates/rue-cli/Cargo.toml +++ b/crates/rue-cli/Cargo.toml @@ -20,6 +20,7 @@ path = "src/main.rs" [dependencies] rue-compiler = { workspace = true } +rue-formatter = { workspace = true } rue-options = { workspace = true } rue-diagnostic = { workspace = true } rue-lir = { workspace = true } @@ -31,3 +32,5 @@ anyhow = { workspace = true } hex = { workspace = true } colored = { workspace = true } toml = { workspace = true } +glob = { workspace = true } +walkdir = { workspace = true } diff --git a/crates/rue-cli/src/fmt.rs b/crates/rue-cli/src/fmt.rs new file mode 100644 index 00000000..c16fe06e --- /dev/null +++ b/crates/rue-cli/src/fmt.rs @@ -0,0 +1,152 @@ +use std::{ + collections::BTreeSet, + env, fs, + path::{Path, PathBuf}, + process, +}; + +use anyhow::{Context, Result, bail}; +use clap::Parser; +use colored::Colorize; +use glob::glob; +use rue_formatter::{FormatOptions, format_source}; +use rue_options::find_project; +use walkdir::WalkDir; + +#[derive(Debug, Parser)] +pub struct FmtArgs { + #[clap(value_name = "PATH_OR_GLOB")] + paths: Vec, + #[clap(long)] + check: bool, +} + +pub fn format(args: &FmtArgs) -> Result<()> { + let current_dir = env::current_dir()?.canonicalize()?; + let targets = resolve_targets(&args.paths, ¤t_dir)?; + let mut pending = Vec::new(); + + for path in targets { + let source = fs::read_to_string(&path) + .with_context(|| format!("Failed to read {}", path.display()))?; + let formatted = format_source(&source, &FormatOptions::default()) + .with_context(|| format!("Failed to format {}", path.display()))?; + if formatted != source { + pending.push((path, formatted)); + } + } + + if args.check { + for (path, _) in &pending { + eprintln!( + "{}", + format!("Needs formatting: {}", display_path(path, ¤t_dir)) + .yellow() + .bold() + ); + } + if !pending.is_empty() { + process::exit(1); + } + return Ok(()); + } + + for (path, formatted) in pending { + fs::write(&path, formatted) + .with_context(|| format!("Failed to write {}", path.display()))?; + println!( + "{}", + format!("Formatted {}", display_path(&path, ¤t_dir)) + .green() + .bold() + ); + } + + Ok(()) +} + +fn resolve_targets(inputs: &[String], current_dir: &Path) -> Result> { + let mut targets = BTreeSet::new(); + + if inputs.is_empty() { + let project = find_project(current_dir, false)?; + let Some(project) = project else { + bail!("No project found"); + }; + collect_path(&project.entrypoint, &mut targets)?; + } else { + for input in inputs { + let path = current_dir.join(input); + if path.exists() { + if !collect_path(&path, &mut targets)? { + bail!("No Rue files found for `{input}`"); + } + continue; + } + + let pattern = if Path::new(input).is_absolute() { + input.clone() + } else { + current_dir.join(input).to_string_lossy().into_owned() + }; + let mut matched = false; + let mut found_rue = false; + for entry in + glob(&pattern).with_context(|| format!("Invalid glob pattern `{input}`"))? + { + let path = + entry.with_context(|| format!("Failed to expand glob pattern `{input}`"))?; + matched = true; + found_rue |= collect_path(&path, &mut targets)?; + } + if !matched { + bail!("Path or glob `{input}` did not match any paths"); + } + if !found_rue { + bail!("No Rue files found for `{input}`"); + } + } + } + + if targets.is_empty() { + bail!("No Rue files found"); + } + + Ok(targets.into_iter().collect()) +} + +fn collect_path(path: &Path, targets: &mut BTreeSet) -> Result { + if path.is_file() { + if !is_rue_file(path) { + bail!("Expected a Rue source file, found {}", path.display()); + } + targets.insert(path.canonicalize()?); + return Ok(true); + } + + if !path.is_dir() { + bail!("Path does not exist: {}", path.display()); + } + + let mut found_rue = false; + for entry in WalkDir::new(path).follow_links(false) { + let entry = entry.with_context(|| format!("Failed to walk {}", path.display()))?; + if entry.file_type().is_file() && is_rue_file(entry.path()) { + targets.insert(entry.path().canonicalize()?); + found_rue = true; + } + } + + Ok(found_rue) +} + +fn is_rue_file(path: &Path) -> bool { + path.extension().is_some_and(|extension| extension == "rue") +} + +fn display_path(path: &Path, current_dir: &Path) -> String { + path.strip_prefix(current_dir) + .unwrap_or(path) + .display() + .to_string() +} diff --git a/crates/rue-cli/src/main.rs b/crates/rue-cli/src/main.rs index 48a151b6..19ef0435 100644 --- a/crates/rue-cli/src/main.rs +++ b/crates/rue-cli/src/main.rs @@ -21,12 +21,17 @@ use rue_diagnostic::{DiagnosticSeverity, SourceKind}; use rue_lir::DebugDialect; use rue_options::{Manifest, find_project}; +mod fmt; + +use fmt::*; + #[derive(Debug, Parser)] pub enum Command { Init(InitArgs), Build(BuildArgs), Test(TestArgs), Debug(DebugArgs), + Fmt(FmtArgs), } #[derive(Debug, Parser)] @@ -73,6 +78,7 @@ fn main() -> Result<()> { Command::Build(args) => build(args), Command::Test(args) => test(args), Command::Debug(args) => debug(args), + Command::Fmt(args) => format(&args), } } diff --git a/crates/rue-lir/src/optimize.rs b/crates/rue-lir/src/optimize.rs index a7856e40..a664a58e 100644 --- a/crates/rue-lir/src/optimize.rs +++ b/crates/rue-lir/src/optimize.rs @@ -1,5 +1,3 @@ -#![allow(clippy::wildcard_imports)] - mod arg_list; mod ops; mod truthy; diff --git a/crates/rue-parser/src/lib.rs b/crates/rue-parser/src/lib.rs index 883d250e..ffcefb7c 100644 --- a/crates/rue-parser/src/lib.rs +++ b/crates/rue-parser/src/lib.rs @@ -3,7 +3,6 @@ mod language; mod parser; mod syntax_kind; -#[allow(clippy::wildcard_imports)] pub(crate) use grammar::*; pub use language::*; From 0cd608938dc327de2383288e4ee02194ca8c7caf Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 19:33:49 -0400 Subject: [PATCH 19/21] LSP support --- Cargo.lock | 2 + crates/rue-cli/src/fmt.rs | 5 ++- crates/rue-formatter/Cargo.toml | 1 + crates/rue-formatter/src/lib.rs | 23 ++-------- crates/rue-lsp/Cargo.toml | 1 + crates/rue-lsp/src/main.rs | 67 +++++++++++++++++++++++++++--- crates/rue-options/src/find.rs | 6 ++- crates/rue-options/src/manifest.rs | 18 ++++++++ 8 files changed, 95 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a4b060c..078baac3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1781,6 +1781,7 @@ dependencies = [ "rue-ast", "rue-diagnostic", "rue-lexer", + "rue-options", "rue-parser", "thiserror 2.0.14", ] @@ -1834,6 +1835,7 @@ dependencies = [ "rowan", "rue-compiler", "rue-diagnostic", + "rue-formatter", "rue-hir", "rue-options", "rue-types", diff --git a/crates/rue-cli/src/fmt.rs b/crates/rue-cli/src/fmt.rs index c16fe06e..44c794fa 100644 --- a/crates/rue-cli/src/fmt.rs +++ b/crates/rue-cli/src/fmt.rs @@ -29,7 +29,10 @@ pub fn format(args: &FmtArgs) -> Result<()> { for path in targets { let source = fs::read_to_string(&path) .with_context(|| format!("Failed to read {}", path.display()))?; - let formatted = format_source(&source, &FormatOptions::default()) + let options = find_project(&path, false) + .with_context(|| format!("Failed to resolve project for {}", path.display()))? + .map_or_else(FormatOptions::default, |project| project.format_options); + let formatted = format_source(&source, &options) .with_context(|| format!("Failed to format {}", path.display()))?; if formatted != source { pending.push((path, formatted)); diff --git a/crates/rue-formatter/Cargo.toml b/crates/rue-formatter/Cargo.toml index 193384dc..dc8e9d80 100644 --- a/crates/rue-formatter/Cargo.toml +++ b/crates/rue-formatter/Cargo.toml @@ -18,6 +18,7 @@ workspace = true rue-ast = { workspace = true } rue-diagnostic = { workspace = true } rue-lexer = { workspace = true } +rue-options = { workspace = true } rue-parser = { workspace = true } rowan = { workspace = true } thiserror = { workspace = true } diff --git a/crates/rue-formatter/src/lib.rs b/crates/rue-formatter/src/lib.rs index b9d26972..062e51ba 100644 --- a/crates/rue-formatter/src/lib.rs +++ b/crates/rue-formatter/src/lib.rs @@ -5,8 +5,8 @@ //! a 100-column target, four-space indentation, LF line endings, no trailing //! whitespace, at most one blank line, and exactly one final newline. //! -//! Configuration files, range formatting, malformed-tree formatting, comment -//! reflow, CLI integration, and LSP integration are intentionally deferred. +//! Range formatting, malformed-tree formatting, and comment reflow are +//! intentionally deferred. mod analysis; mod document; @@ -23,6 +23,7 @@ use std::sync::Arc; use rue_ast::{AstDocument, AstNode}; use rue_diagnostic::{Diagnostic, Source, SourceKind}; use rue_lexer::Lexer; +pub use rue_options::FormatOptions; use rue_parser::{Parser, SyntaxKind, SyntaxNode}; use thiserror::Error; @@ -31,24 +32,6 @@ use crate::{ token_stream::TokenStream, }; -/// Deterministic formatter settings. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct FormatOptions { - /// Preferred maximum line width. - pub max_width: usize, - /// Number of spaces in one indentation level. - pub indent_width: usize, -} - -impl Default for FormatOptions { - fn default() -> Self { - Self { - max_width: 100, - indent_width: 4, - } - } -} - /// A failure that prevents a safe formatting result. #[derive(Debug, Error)] pub enum FormatError { diff --git a/crates/rue-lsp/Cargo.toml b/crates/rue-lsp/Cargo.toml index b2728f47..1ac8d4ff 100644 --- a/crates/rue-lsp/Cargo.toml +++ b/crates/rue-lsp/Cargo.toml @@ -17,6 +17,7 @@ workspace = true [dependencies] rue-compiler = { workspace = true } rue-diagnostic = { workspace = true } +rue-formatter = { workspace = true } rue-options = { workspace = true } rue-hir = { workspace = true } rue-types = { workspace = true } diff --git a/crates/rue-lsp/src/main.rs b/crates/rue-lsp/src/main.rs index 21a0ec01..5746ced8 100644 --- a/crates/rue-lsp/src/main.rs +++ b/crates/rue-lsp/src/main.rs @@ -6,20 +6,22 @@ use cache::Cache; use indexmap::IndexMap; use rue_compiler::{Compiler, FileTree, normalize_path}; use rue_diagnostic::SourceKind; +use rue_formatter::{FormatError, FormatOptions, format_source}; use std::collections::HashMap; +use std::fs; use std::sync::{Arc, Mutex}; use rue_options::find_project; use send_wrapper::SendWrapper; -use tower_lsp::jsonrpc::Result; +use tower_lsp::jsonrpc::{Error, Result}; use tower_lsp::lsp_types::{ CompletionOptions, CompletionParams, CompletionResponse, Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, - GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, - HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, LanguageString, - Location, MarkedString, MessageType, OneOf, Position, Range, ReferenceParams, - ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, Url, + DocumentFormattingParams, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, + HoverParams, HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, + LanguageString, Location, MarkedString, MessageType, OneOf, Position, Range, ReferenceParams, + ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url, }; use tower_lsp::{Client, LanguageServer, LspService, Server}; @@ -43,6 +45,7 @@ impl LanguageServer for Backend { hover_provider: Some(HoverProviderCapability::Simple(true)), definition_provider: Some(OneOf::Left(true)), references_provider: Some(OneOf::Left(true)), + document_formatting_provider: Some(OneOf::Left(true)), completion_provider: Some(CompletionOptions { trigger_characters: Some(vec![ ".".to_string(), @@ -73,7 +76,8 @@ impl LanguageServer for Backend { } async fn did_open(&self, params: DidOpenTextDocumentParams) { - self.on_change(params.text_document.uri, None).await; + self.on_change(params.text_document.uri, Some(params.text_document.text)) + .await; } async fn did_change(&self, params: DidChangeTextDocumentParams) { @@ -107,6 +111,10 @@ impl LanguageServer for Backend { Ok(self.on_completion(¶ms)) } + async fn formatting(&self, params: DocumentFormattingParams) -> Result>> { + self.on_formatting(¶ms).map(Some) + } + async fn shutdown(&self) -> Result<()> { Ok(()) } @@ -187,6 +195,27 @@ impl Backend { diagnostics } + fn on_formatting(&self, params: &DocumentFormattingParams) -> Result> { + let uri = ¶ms.text_document.uri; + let path = uri + .to_file_path() + .map_err(|()| Error::invalid_params("document URI is not a file"))?; + let source_kind = + normalize_path(&path).map_err(|error| Error::invalid_params(error.to_string()))?; + let project = + find_project(&path, false).map_err(|error| Error::invalid_params(error.to_string()))?; + let options = project.map_or_else(FormatOptions::default, |project| project.format_options); + let source = self.file_cache.lock().unwrap().get(&source_kind).cloned(); + let source = match source { + Some(source) => source, + None => fs::read_to_string(&path) + .map_err(|error| Error::invalid_params(error.to_string()))?, + }; + + formatting_edits(&source, &options) + .map_err(|error| Error::invalid_params(error.to_string())) + } + fn on_hover(&self, params: &HoverParams) -> Option { let mut cache = self .cache @@ -292,6 +321,32 @@ impl Backend { } } +fn formatting_edits( + source: &str, + options: &FormatOptions, +) -> std::result::Result, FormatError> { + let formatted = format_source(source, options)?; + if formatted == source { + return Ok(Vec::new()); + } + + Ok(vec![TextEdit::new( + Range::new(Position::new(0, 0), document_end(source)), + formatted, + )]) +} + +fn document_end(source: &str) -> Position { + let line = source.bytes().filter(|byte| *byte == b'\n').count(); + let last_line = source + .rsplit_once('\n') + .map_or(source, |(_, last_line)| last_line); + let last_line = last_line.strip_suffix('\r').unwrap_or(last_line); + let character = last_line.encode_utf16().count(); + + Position::new(line.try_into().unwrap(), character.try_into().unwrap()) +} + fn diagnostic(diagnostic: &rue_diagnostic::Diagnostic) -> Diagnostic { let start = diagnostic.start(); let end = diagnostic.end(); diff --git a/crates/rue-options/src/find.rs b/crates/rue-options/src/find.rs index c8a7a1aa..9efd66e4 100644 --- a/crates/rue-options/src/find.rs +++ b/crates/rue-options/src/find.rs @@ -5,7 +5,7 @@ use std::{ use thiserror::Error; -use crate::{CompilerOptions, Manifest}; +use crate::{CompilerOptions, FormatOptions, Manifest}; #[derive(Debug, Error)] pub enum Error { @@ -23,6 +23,7 @@ pub enum Error { pub struct Project { pub manifest: Option, pub options: CompilerOptions, + pub format_options: FormatOptions, pub entrypoint: PathBuf, } @@ -45,6 +46,7 @@ pub fn find_project(path: &Path, debug: bool) -> Result, Error> } return Ok(Some(Project { + format_options: manifest.formatter, manifest: Some(manifest), options, entrypoint, @@ -69,6 +71,7 @@ pub fn find_project(path: &Path, debug: bool) -> Result, Error> } else { CompilerOptions::default() }, + format_options: FormatOptions::default(), entrypoint: path.to_path_buf(), })); } @@ -81,6 +84,7 @@ pub fn find_project(path: &Path, debug: bool) -> Result, Error> } else { CompilerOptions::default() }, + format_options: FormatOptions::default(), entrypoint: path.to_path_buf(), })); } diff --git a/crates/rue-options/src/manifest.rs b/crates/rue-options/src/manifest.rs index 6f02c3f8..bc0f0e0c 100644 --- a/crates/rue-options/src/manifest.rs +++ b/crates/rue-options/src/manifest.rs @@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Manifest { pub compiler: CompilerSection, + #[serde(default)] + pub formatter: FormatOptions, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -27,3 +29,19 @@ impl Default for CompilerSection { } } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default)] +pub struct FormatOptions { + pub max_width: usize, + pub indent_width: usize, +} + +impl Default for FormatOptions { + fn default() -> Self { + Self { + max_width: 100, + indent_width: 4, + } + } +} From bee7844a6e2bcba9557963168003940058e5149b Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 19:40:20 -0400 Subject: [PATCH 20/21] Fix --- crates/rue-formatter/src/emit.rs | 45 +++++++++++++------------------ crates/rue-formatter/src/tests.rs | 14 +++++++++- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/crates/rue-formatter/src/emit.rs b/crates/rue-formatter/src/emit.rs index 6bb87609..e3d53bc4 100644 --- a/crates/rue-formatter/src/emit.rs +++ b/crates/rue-formatter/src/emit.rs @@ -321,32 +321,25 @@ impl<'a> Formatter<'a> { || comment.multiline || trailing_gap.newlines > 0 }); - let leading = self.gap_doc_with_comments( - leading_gap, - match style { - DelimiterStyle::Block => Separator::Hard, - DelimiterStyle::Braced | DelimiterStyle::ConditionalBraced => Separator::Soft, - DelimiterStyle::Group - | DelimiterStyle::Fill - | DelimiterStyle::FillBraced - | DelimiterStyle::Hug - | DelimiterStyle::Vertical => Separator::None, - }, - true, - false, - ); - let trailing = self.gap_doc( - trailing_gap, - match style { - DelimiterStyle::Block => Separator::Hard, - DelimiterStyle::Braced | DelimiterStyle::ConditionalBraced => Separator::Soft, - DelimiterStyle::Group - | DelimiterStyle::Fill - | DelimiterStyle::FillBraced - | DelimiterStyle::Hug - | DelimiterStyle::Vertical => Separator::None, - }, - ); + let delimiter_separator = match style { + DelimiterStyle::Block => Separator::Hard, + DelimiterStyle::Braced | DelimiterStyle::ConditionalBraced => Separator::Soft, + DelimiterStyle::Group + | DelimiterStyle::Fill + | DelimiterStyle::FillBraced + | DelimiterStyle::Hug + | DelimiterStyle::Vertical => Separator::None, + }; + let leading = if leading_gap.comments.is_empty() { + separator_doc(delimiter_separator) + } else { + self.gap_doc_with_comments(leading_gap, delimiter_separator, true, false) + }; + let trailing = if trailing_gap.comments.is_empty() { + separator_doc(delimiter_separator) + } else { + self.gap_doc(trailing_gap, delimiter_separator) + }; Ok(match style { DelimiterStyle::Block | DelimiterStyle::Braced | DelimiterStyle::ConditionalBraced => { diff --git a/crates/rue-formatter/src/tests.rs b/crates/rue-formatter/src/tests.rs index 28f9e164..2f0ee744 100644 --- a/crates/rue-formatter/src/tests.rs +++ b/crates/rue-formatter/src/tests.rs @@ -48,7 +48,7 @@ fn types_bindings_generics_and_imports() { #[test] fn preserves_one_intentional_blank_line() { check( - "fn main(){let a=1;\n\n\nlet b=2;\na+b}", + "fn main(){\n\nlet a=1;\n\n\nlet b=2;\na+b\n\n}", expect![[r#" fn main() { let a = 1; @@ -60,6 +60,18 @@ fn preserves_one_intentional_blank_line() { ); } +#[test] +fn removes_blank_lines_adjacent_to_braced_delimiters() { + check( + "fn main()->List{[CreateCoinAnnouncement{\n\nmessage:nil,\n\n},]}", + expect![[r#" + fn main() -> List { + [CreateCoinAnnouncement { message: nil }] + } + "#]], + ); +} + #[test] fn width_breaks_delimited_groups() { let options = FormatOptions { From 02f7ba2f1605f6426bef7fb61ed86e009402f841 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 26 Jul 2026 19:42:42 -0400 Subject: [PATCH 21/21] Bump to 0.10.0 --- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 20 ++++++++++---------- crates/rue-ast/Cargo.toml | 2 +- crates/rue-cli/Cargo.toml | 2 +- crates/rue-compiler/Cargo.toml | 2 +- crates/rue-diagnostic/Cargo.toml | 2 +- crates/rue-formatter/Cargo.toml | 2 +- crates/rue-hir/Cargo.toml | 2 +- crates/rue-lexer/Cargo.toml | 2 +- crates/rue-lir/Cargo.toml | 2 +- crates/rue-lsp/Cargo.toml | 2 +- crates/rue-options/Cargo.toml | 2 +- crates/rue-parser/Cargo.toml | 2 +- crates/rue-tests/Cargo.toml | 2 +- crates/rue-types/Cargo.toml | 2 +- wasm/Cargo.toml | 2 +- 16 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 078baac3..1f19083f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1712,7 +1712,7 @@ dependencies = [ [[package]] name = "rue-ast" -version = "0.9.0" +version = "0.10.0" dependencies = [ "num-traits", "paste", @@ -1721,7 +1721,7 @@ dependencies = [ [[package]] name = "rue-cli" -version = "0.9.0" +version = "0.10.0" dependencies = [ "anyhow", "chialisp", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "rue-compiler" -version = "0.9.0" +version = "0.10.0" dependencies = [ "clvmr", "expect-test", @@ -1767,14 +1767,14 @@ dependencies = [ [[package]] name = "rue-diagnostic" -version = "0.9.0" +version = "0.10.0" dependencies = [ "thiserror 2.0.14", ] [[package]] name = "rue-formatter" -version = "0.9.0" +version = "0.10.0" dependencies = [ "expect-test", "rowan", @@ -1788,7 +1788,7 @@ dependencies = [ [[package]] name = "rue-hir" -version = "0.9.0" +version = "0.10.0" dependencies = [ "derive_more", "expect-test", @@ -1805,14 +1805,14 @@ dependencies = [ [[package]] name = "rue-lexer" -version = "0.9.0" +version = "0.10.0" dependencies = [ "expect-test", ] [[package]] name = "rue-lir" -version = "0.9.0" +version = "0.10.0" dependencies = [ "chialisp", "clvm-traits", @@ -1829,7 +1829,7 @@ dependencies = [ [[package]] name = "rue-lsp" -version = "0.9.0" +version = "0.10.0" dependencies = [ "indexmap", "rowan", @@ -1846,7 +1846,7 @@ dependencies = [ [[package]] name = "rue-options" -version = "0.9.0" +version = "0.10.0" dependencies = [ "serde", "thiserror 2.0.14", @@ -1855,7 +1855,7 @@ dependencies = [ [[package]] name = "rue-parser" -version = "0.9.0" +version = "0.10.0" dependencies = [ "derive_more", "expect-test", @@ -1870,7 +1870,7 @@ dependencies = [ [[package]] name = "rue-tests" -version = "0.9.0" +version = "0.10.0" dependencies = [ "anyhow", "chialisp", @@ -1888,7 +1888,7 @@ dependencies = [ [[package]] name = "rue-types" -version = "0.9.0" +version = "0.10.0" dependencies = [ "clvmr", "derive_more", @@ -1903,7 +1903,7 @@ dependencies = [ [[package]] name = "rue-wasm" -version = "0.9.0" +version = "0.10.0" dependencies = [ "chialisp", "clvmr", diff --git a/Cargo.toml b/Cargo.toml index 00e0e236..d8995ae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,16 +43,16 @@ case_sensitive_file_extension_comparisons = "allow" wildcard_imports = "allow" [workspace.dependencies] -rue-lexer = { path = "crates/rue-lexer", version = "0.9.0" } -rue-parser = { path = "crates/rue-parser", version = "0.9.0" } -rue-diagnostic = { path = "crates/rue-diagnostic", version = "0.9.0" } -rue-ast = { path = "crates/rue-ast", version = "0.9.0" } -rue-formatter = { path = "crates/rue-formatter", version = "0.9.0" } -rue-compiler = { path = "crates/rue-compiler", version = "0.9.0" } -rue-options = { path = "crates/rue-options", version = "0.9.0" } -rue-lir = { path = "crates/rue-lir", version = "0.9.0" } -rue-hir = { path = "crates/rue-hir", version = "0.9.0" } -rue-types = { path = "crates/rue-types", version = "0.9.0" } +rue-lexer = { path = "crates/rue-lexer", version = "0.10.0" } +rue-parser = { path = "crates/rue-parser", version = "0.10.0" } +rue-diagnostic = { path = "crates/rue-diagnostic", version = "0.10.0" } +rue-ast = { path = "crates/rue-ast", version = "0.10.0" } +rue-formatter = { path = "crates/rue-formatter", version = "0.10.0" } +rue-compiler = { path = "crates/rue-compiler", version = "0.10.0" } +rue-options = { path = "crates/rue-options", version = "0.10.0" } +rue-lir = { path = "crates/rue-lir", version = "0.10.0" } +rue-hir = { path = "crates/rue-hir", version = "0.10.0" } +rue-types = { path = "crates/rue-types", version = "0.10.0" } anyhow = "1.0.98" clvm-traits = "0.28.1" clvm-utils = "0.28.1" diff --git a/crates/rue-ast/Cargo.toml b/crates/rue-ast/Cargo.toml index f873c2c7..6a7b7aff 100644 --- a/crates/rue-ast/Cargo.toml +++ b/crates/rue-ast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-ast" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "An implementation of the Abstract Syntax Tree for the Rue compiler." diff --git a/crates/rue-cli/Cargo.toml b/crates/rue-cli/Cargo.toml index 8f67d7d6..0d188ce7 100644 --- a/crates/rue-cli/Cargo.toml +++ b/crates/rue-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-cli" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "A CLI tool for invoking the Rue compiler." diff --git a/crates/rue-compiler/Cargo.toml b/crates/rue-compiler/Cargo.toml index fb3d55fd..cb41f9df 100644 --- a/crates/rue-compiler/Cargo.toml +++ b/crates/rue-compiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-compiler" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "A compiler for the Rue programming language." diff --git a/crates/rue-diagnostic/Cargo.toml b/crates/rue-diagnostic/Cargo.toml index 26d430a7..4bcbd725 100644 --- a/crates/rue-diagnostic/Cargo.toml +++ b/crates/rue-diagnostic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-diagnostic" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "All of the potential diagnostics that can be emitted by the Rue compiler." diff --git a/crates/rue-formatter/Cargo.toml b/crates/rue-formatter/Cargo.toml index dc8e9d80..fd6b37d7 100644 --- a/crates/rue-formatter/Cargo.toml +++ b/crates/rue-formatter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-formatter" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "An AST formatter for the Rue programming language." diff --git a/crates/rue-hir/Cargo.toml b/crates/rue-hir/Cargo.toml index 91bbdca8..d560762c 100644 --- a/crates/rue-hir/Cargo.toml +++ b/crates/rue-hir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-hir" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "Provides a high-level intermediate representation of the Rue programming language." diff --git a/crates/rue-lexer/Cargo.toml b/crates/rue-lexer/Cargo.toml index ee6a51bb..40425b7c 100644 --- a/crates/rue-lexer/Cargo.toml +++ b/crates/rue-lexer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-lexer" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "A lexer for the Rue programming language." diff --git a/crates/rue-lir/Cargo.toml b/crates/rue-lir/Cargo.toml index c18fd15e..b9807654 100644 --- a/crates/rue-lir/Cargo.toml +++ b/crates/rue-lir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-lir" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "Provides a low-level intermediate representation that compiles to CLVM." diff --git a/crates/rue-lsp/Cargo.toml b/crates/rue-lsp/Cargo.toml index 1ac8d4ff..d881eb8b 100644 --- a/crates/rue-lsp/Cargo.toml +++ b/crates/rue-lsp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-lsp" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "A language server protocol (LSP) implementation for the Rue programming language." diff --git a/crates/rue-options/Cargo.toml b/crates/rue-options/Cargo.toml index 910f6864..ef2db25c 100644 --- a/crates/rue-options/Cargo.toml +++ b/crates/rue-options/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-options" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "Provides a way to configure the Rue compiler." diff --git a/crates/rue-parser/Cargo.toml b/crates/rue-parser/Cargo.toml index 3b70ed02..16fe0226 100644 --- a/crates/rue-parser/Cargo.toml +++ b/crates/rue-parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-parser" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "A parser for the Rue programming language." diff --git a/crates/rue-tests/Cargo.toml b/crates/rue-tests/Cargo.toml index 758fff7a..628ab23f 100644 --- a/crates/rue-tests/Cargo.toml +++ b/crates/rue-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-tests" -version = "0.9.0" +version = "0.10.0" edition = "2024" publish = false license = "Apache-2.0" diff --git a/crates/rue-types/Cargo.toml b/crates/rue-types/Cargo.toml index 5a859c9c..6490241c 100644 --- a/crates/rue-types/Cargo.toml +++ b/crates/rue-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-types" -version = "0.9.0" +version = "0.10.0" edition = "2024" license = "Apache-2.0" description = "A type system for the Rue programming language." diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index c76e31c3..b2dbc817 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rue-wasm" -version = "0.9.0" +version = "0.10.0" edition = "2024" publish = false license = "Apache-2.0"