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 3362d850..1f19083f 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" @@ -1712,15 +1712,16 @@ dependencies = [ [[package]] name = "rue-ast" -version = "0.9.0" +version = "0.10.0" dependencies = [ + "num-traits", "paste", "rue-parser", ] [[package]] name = "rue-cli" -version = "0.9.0" +version = "0.10.0" dependencies = [ "anyhow", "chialisp", @@ -1728,17 +1729,20 @@ dependencies = [ "clvm-utils", "clvmr", "colored", + "glob", "hex", "rue-compiler", "rue-diagnostic", + "rue-formatter", "rue-lir", "rue-options", "toml", + "walkdir", ] [[package]] name = "rue-compiler" -version = "0.9.0" +version = "0.10.0" dependencies = [ "clvmr", "expect-test", @@ -1763,14 +1767,28 @@ dependencies = [ [[package]] name = "rue-diagnostic" -version = "0.9.0" +version = "0.10.0" +dependencies = [ + "thiserror 2.0.14", +] + +[[package]] +name = "rue-formatter" +version = "0.10.0" dependencies = [ + "expect-test", + "rowan", + "rue-ast", + "rue-diagnostic", + "rue-lexer", + "rue-options", + "rue-parser", "thiserror 2.0.14", ] [[package]] name = "rue-hir" -version = "0.9.0" +version = "0.10.0" dependencies = [ "derive_more", "expect-test", @@ -1787,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", @@ -1811,12 +1829,13 @@ dependencies = [ [[package]] name = "rue-lsp" -version = "0.9.0" +version = "0.10.0" dependencies = [ "indexmap", "rowan", "rue-compiler", "rue-diagnostic", + "rue-formatter", "rue-hir", "rue-options", "rue-types", @@ -1827,7 +1846,7 @@ dependencies = [ [[package]] name = "rue-options" -version = "0.9.0" +version = "0.10.0" dependencies = [ "serde", "thiserror 2.0.14", @@ -1836,7 +1855,7 @@ dependencies = [ [[package]] name = "rue-parser" -version = "0.9.0" +version = "0.10.0" dependencies = [ "derive_more", "expect-test", @@ -1851,7 +1870,7 @@ dependencies = [ [[package]] name = "rue-tests" -version = "0.9.0" +version = "0.10.0" dependencies = [ "anyhow", "chialisp", @@ -1860,6 +1879,7 @@ dependencies = [ "indexmap", "rue-compiler", "rue-diagnostic", + "rue-formatter", "rue-lir", "rue-options", "serde", @@ -1868,7 +1888,7 @@ dependencies = [ [[package]] name = "rue-types" -version = "0.9.0" +version = "0.10.0" dependencies = [ "clvmr", "derive_more", @@ -1883,7 +1903,7 @@ dependencies = [ [[package]] name = "rue-wasm" -version = "0.9.0" +version = "0.10.0" dependencies = [ "chialisp", "clvmr", @@ -1940,6 +1960,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" @@ -2524,6 +2553,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" @@ -2600,6 +2639,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 525500d9..d8995ae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,17 +40,19 @@ 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" } -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-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" @@ -75,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-ast/Cargo.toml b/crates/rue-ast/Cargo.toml index 4f4b3360..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." @@ -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-cli/Cargo.toml b/crates/rue-cli/Cargo.toml index 808e9811..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." @@ -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..44c794fa --- /dev/null +++ b/crates/rue-cli/src/fmt.rs @@ -0,0 +1,155 @@ +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 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)); + } + } + + 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-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-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/crates/rue-compiler/src/std/conditions/types.rue b/crates/rue-compiler/src/std/conditions/types.rue index 621f247c..7e6d1598 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 +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..2e65df81 100644 --- a/crates/rue-compiler/src/std/curry_tree_hash.rue +++ b/crates/rue-compiler/src/std/curry_tree_hash.rue @@ -9,14 +9,19 @@ 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 { @@ -26,9 +31,6 @@ fn curried_params_hash(parameters: List) -> Bytes32 { 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..9aefcfc4 100644 --- a/crates/rue-compiler/src/std/merkle.rue +++ b/crates/rue-compiler/src/std/merkle.rue @@ -15,56 +15,31 @@ export fn calculate_merkle_root( if additional_steps is nil { return current_hash; } - + 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 + 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; } 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 - } + 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..445f7c6f 100644 --- a/crates/rue-compiler/src/std/prelude.rue +++ b/crates/rue-compiler/src/std/prelude.rue @@ -1,10 +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; 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 new file mode 100644 index 00000000..fd6b37d7 --- /dev/null +++ b/crates/rue-formatter/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "rue-formatter" +version = "0.10.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-options = { 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/analysis.rs b/crates/rue-formatter/src/analysis.rs new file mode 100644 index 00000000..455d4d30 --- /dev/null +++ b/crates/rue-formatter/src/analysis.rs @@ -0,0 +1,625 @@ +use rue_ast::{AstDocument, AstExpr, AstNode, AstNodeKind}; +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, + ConditionalBraced, + Group, + Fill, + FillBraced, + Hug, + Vertical, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SingleArgumentLayout { + Hug, + Vertical, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ItemBoundary { + Document, + Module, + 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) continuation_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; + const CONDITIONAL_GROUP: u8 = 1 << 5; + + 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) + } + + pub(crate) fn is_conditional_group(&self) -> bool { + self.flags.contains(TokenFlags::CONDITIONAL_GROUP) + } +} + +#[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.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 (id, token) in stream.token_ids() { + 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() { + 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 => { + 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 { + facts[id.index()].flags.insert(TokenFlags::TRAILING_COMMA); + } + } + } + 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); + } + } + 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) + .find(|token| matches!(token.kind(), T!['('] | T!['['] | T!['{'])) + { + 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 => { + 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); + } + } + AstNodeKind::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); + } + } + AstNodeKind::FunctionItem + | AstNodeKind::FunctionCallExpr + | AstNodeKind::LambdaExpr + | AstNodeKind::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); + 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 => { + 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); + } + } + 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::ListItem + | AstNodeKind::BinaryExpr + | 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(()) +} + +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::IfStmt + ) || (parent.kind() == SyntaxKind::IfExpr && !is_inline_conditional(&parent)) + }) + { + 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())?; + 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 { + DelimiterStyle::Braced + }) +} + +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; + }; + ((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, + 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 let Some(next) = stream + .next_token(id) + .filter(|next| stream.token(*next).kind == T![;]) + { + 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 operators = Vec::new(); + if node.kind() == SyntaxKind::BinaryExpr { + collect_binary_operators(&node, None, 0, &mut operators)?; + } else { + collect_union_operators(&node, &mut operators); + } + facts[start.index()].continuation_operators = operators + .into_iter() + .map(|operator| { + Ok(ContinuationOperator { + token: stream.token_id_at_offset(operator.offset)?, + depth: operator.depth, + }) + }) + .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 ast_node_kind(node: &SyntaxNode) -> Result { + AstNodeKind::of(node).ok_or_else(|| { + FormatError::Internal(format!( + "syntax node {:?} has no typed AST node kind", + node.kind() + )) + }) +} + +#[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_operators(&child, Some(precedence), depth, operators)?; + } + rowan::NodeOrToken::Token(token) if SyntaxKind::BINARY_OPS.contains(&token.kind()) => { + operators.push(OperatorOffset { + offset: usize::from(token.text_range().start()), + depth, + }); + } + _ => {} + } + } + Ok(()) +} + +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_operators(&child, operators); + } + rowan::NodeOrToken::Token(token) if token.kind() == T![|] => { + operators.push(OperatorOffset { + offset: usize::from(token.text_range().start()), + depth: 0, + }); + } + _ => {} + } + } +} + +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 new file mode 100644 index 00000000..a703c57f --- /dev/null +++ b/crates/rue-formatter/src/document.rs @@ -0,0 +1,108 @@ +//! A deliberately small, private pretty-printing document model. + +#[derive(Debug, Clone)] +pub(crate) enum Doc { + Nil, + Text(String), + 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 { + flat: Box, + broken: Box, + space_when_flat: bool, + indent_levels_on_break: usize, + }, + 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 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_levels_on_break, + } + } + + pub(crate) fn fill_choice( + flat: Self, + broken: Self, + space_when_flat: bool, + indent_levels_on_break: usize, + ) -> Self { + Self::Fill { + flat: Box::new(flat), + broken: Box::new(broken), + space_when_flat, + indent_levels_on_break, + } + } + + 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 outdent(self) -> Self { + Self::Outdent(Box::new(self)) + } + + pub(crate) fn group(self) -> Self { + Self::Group(Box::new(self)) + } + + 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/emit.rs b/crates/rue-formatter/src/emit.rs new file mode 100644 index 00000000..e3d53bc4 --- /dev/null +++ b/crates/rue-formatter/src/emit.rs @@ -0,0 +1,645 @@ +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.len() { + return Err(FormatError::Internal(format!( + "emitted {} of {} significant tokens", + self.consumed_tokens, + self.stream.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 { + 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()) + .continuation_operators + .is_empty() + { + 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); + } + } + + 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() && self.stream.boundary_after(group_end) == span.end()) + { + 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.continuation_operators.is_empty() + && 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( + 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( + self.stream.span(index, self.stream.boundary_before(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 + }; + + 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()).is_conditional_group() { + return Ok(self.span_inner(span, false)?.group()); + } + 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()) + .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.token), + )?, + false, + )?]; + for (position, operator) in operators.iter().enumerate() { + 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.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.token).kind) + && self.stream.gap_before(operator.token).comments.is_empty() + { + 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.token), Separator::Soft); + docs.push(indent_levels( + Doc::concat([before_operator, segment]), + operator.depth + 1, + )); + } + } + 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 = 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( + 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 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 suppress_broken_comma = style == DelimiterStyle::Hug; + 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(inner_end), Separator::None); + self.consumed_tokens += 1; + 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 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 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 => { + 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 }; + 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, 1), + ]) + .group() + } + DelimiterStyle::Group | DelimiterStyle::Vertical => Doc::concat([ + open_doc, + Doc::concat([ + if leading_comment_starts_line { + Doc::Nil + } else { + Doc::if_break(Doc::hard_line(), Doc::Nil) + }, + leading, + inner, + comma, + ]) + .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 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 + && 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 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, + 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..03806adb --- /dev/null +++ b/crates/rue-formatter/src/equivalence.rs @@ -0,0 +1,156 @@ +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.end_boundary().index()); + + 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 (boundary, gap) in stream.gaps() { + let gap_index = boundary.index(); + 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.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 { + span.start().index() < gap && gap < span.end().index() +} + +fn find_close( + open: crate::token_stream::TokenId, + stream: &TokenStream, +) -> Result { + let mut depth = 0; + 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 Ok(id); + } + } + _ => {} + } + } + 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 new file mode 100644 index 00000000..6e1fb02c --- /dev/null +++ b/crates/rue-formatter/src/format.rs @@ -0,0 +1,13 @@ +use rue_ast::AstDocument; + +use crate::{ + FormatError, analysis::Layout, document::Doc, emit::Formatter, token_stream::TokenStream, +}; + +pub(crate) fn format_document( + document: &AstDocument, + stream: &TokenStream, +) -> Result { + 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 new file mode 100644 index 00000000..062e51ba --- /dev/null +++ b/crates/rue-formatter/src/lib.rs @@ -0,0 +1,241 @@ +//! 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. +//! +//! Range formatting, malformed-tree formatting, and comment reflow are +//! intentionally deferred. + +mod analysis; +mod document; +mod emit; +mod equivalence; +mod format; +mod ordering; +mod renderer; +mod token_stream; +mod trivia; + +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; + +use crate::{ + equivalence::comment_signature, format::format_document, renderer::render, + token_stream::TokenStream, +}; + +/// 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 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 { + /// 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(format!( + "formatting was not idempotent\nfirst:\n{formatted}\nsecond:\n{second}" + ))); + } + + 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.document, &before_stream)?; + let after_comments = comment_signature(&after.document, &after_stream)?; + if before_comments != after_comments { + return Err(FormatError::Internal(format!( + "formatted source changed or duplicated comments\nbefore: {before_comments:#?}\nafter: {after_comments:#?}" + ))); + } + Ok(()) +} + +fn structural_signature(root: &SyntaxNode) -> Vec { + let mut signature = Vec::new(); + 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(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(_) => {} + } + } +} + +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 + ) + }) +} + +#[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..7f000990 --- /dev/null +++ b/crates/rue-formatter/src/ordering.rs @@ -0,0 +1,332 @@ +use std::{collections::HashMap, fmt::Write}; + +use rue_ast::{AstDocument, AstFunctionItem, AstItem, AstNode}; +use rue_parser::{SyntaxKind, SyntaxNode, T}; + +use crate::{ + FormatError, + token_stream::{TokenId, TokenSpan, TokenStream}, + trivia::{Trivia, split_between, split_file_header, split_group_opening}, +}; + +#[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, Clone, Copy, PartialEq, Eq)] +pub(crate) enum CompactGroup { + Constant, + TypeAlias, + ExternDeclaration, +} + +#[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 = 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() + .filter(|token| !matches!(token.kind, T![import] | T![export] | T![;])) + .map(|token| token.text.as_str()) + .collect::(); + let keyword_key = stream.token(span.start()).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.first_gap().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.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 + .token_at(span.end()) + .filter(|id| stream.token(*id).kind == T![,]); + let sort_key = stream + .tokens_in(span) + .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_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 (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, + items, + closing: Trivia::dangling(closing.gap), + }, + ); + } + Ok(groups) +} + +fn item_span(node: &SyntaxNode, stream: &TokenStream) -> Result { + 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 { + 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 last = stream.token_id_at_offset(usize::from(last.text_range().start()))?; + stream.span_through(start, last) +} + +fn find_matching_close(open: TokenId, stream: &TokenStream) -> Result { + let mut depth = 0; + for (id, token) in stream.token_ids().skip(open.index()) { + match token.kind { + T!['{'] => depth += 1, + T!['}'] => { + depth -= 1; + if depth == 0 { + return Ok(id); + } + } + _ => {} + } + } + 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 new file mode 100644 index 00000000..ca4f1794 --- /dev/null +++ b/crates/rue-formatter/src/renderer.rs @@ -0,0 +1,284 @@ +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::Fill { + flat, + broken, + space_when_flat, + indent_levels_on_break, + } => { + let separator_width = usize::from(*space_when_flat); + if column + separator_width <= options.max_width + && fits( + options.max_width - column - separator_width, + command.indent, + flat, + &commands, + Mode::Broken, + ) + { + if *space_when_flat { + output.push(' '); + column += 1; + } + commands.push(Command { + indent: command.indent, + mode: command.mode, + doc: flat, + }); + } else { + output.push('\n'); + let indent = command.indent + indent_levels_on_break * options.indent_width; + output.extend(std::iter::repeat_n(' ', indent)); + column = indent; + commands.push(Command { + indent, + mode: command.mode, + doc: broken, + }); + } + } + 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::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( + options.max_width.saturating_sub(column), + command.indent, + doc, + &commands, + Mode::Flat, + ) { + 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::Outdent(doc) | Doc::Group(doc) => has_forced_line(doc), + Doc::Fill { flat, .. } => has_forced_line(flat), + Doc::IfBreak { flat, .. } => has_forced_line(flat), + } +} + +fn fits( + mut remaining: usize, + 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: initial_mode, + 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::Fill { + flat, + space_when_flat, + .. + } => { + let separator_width = usize::from(*space_when_flat); + if separator_width > remaining { + return false; + } + remaining -= separator_width; + commands.push(Command { + indent: command.indent, + mode: command.mode, + doc: flat, + }); + } + Doc::Line(LineKind::Soft) => return true, + Doc::Line(LineKind::Hard | LineKind::Empty) => return true, + 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, + doc: if command.mode == Mode::Broken { + broken + } else { + flat + }, + }), + } + } + + 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 +} + +#[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"), 0), + 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"), 1), + 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/tests.rs b/crates/rue-formatter/src/tests.rs new file mode 100644 index 00000000..2f0ee744 --- /dev/null +++ b/crates/rue-formatter/src/tests.rs @@ -0,0 +1,1307 @@ +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"); + 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 preserves_one_intentional_blank_line() { + check( + "fn main(){\n\nlet a=1;\n\n\nlet b=2;\na+b\n\n}", + expect![[r#" + fn main() { + let a = 1; + + let b = 2; + a + b + } + "#]], + ); +} + +#[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 { + 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_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<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 { + 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); +} + +#[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 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 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() {} + "#]], + ); +} + +#[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) + } + "#]], + ); +} + +#[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 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( + "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 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( + "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 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( + 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 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 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 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/tests/comments.rs b/crates/rue-formatter/src/tests/comments.rs new file mode 100644 index 00000000..384ac7bf --- /dev/null +++ b/crates/rue-formatter/src/tests/comments.rs @@ -0,0 +1,140 @@ +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 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 { + 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..4b891da8 --- /dev/null +++ b/crates/rue-formatter/src/tests/imports.rs @@ -0,0 +1,132 @@ +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 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( + "// 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 new file mode 100644 index 00000000..b59117d6 --- /dev/null +++ b/crates/rue-formatter/src/token_stream.rs @@ -0,0 +1,403 @@ +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 index(self) -> usize { + self.0 + } +} + +#[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 { + start: TokenId, + end: TokenBoundary, +} + +impl TokenSpan { + pub(crate) fn start(self) -> TokenId { + self.start + } + + pub(crate) fn end(self) -> TokenBoundary { + self.end + } +} + +#[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) 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 { + tokens: Vec, + gaps: Vec, + pub(crate) comment_count: usize, + token_by_offset: HashMap, +} + +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, + placement: if line_has_significant && pending_newlines == 0 { + CommentPlacement::Trailing + } else { + CommentPlacement::Leading + }, + multiline, + }); + comment_count += 1; + pending_newlines = 0; + if kind == SyntaxKind::LineComment { + pending_newlines = newline_count(raw.text()); + line_has_significant = false; + } else if multiline { + line_has_significant = false; + } + } + kind if kind.is_trivia() => { + return Err(FormatError::UnsupportedTrivia(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(), + )); + } + + let token_by_offset = tokens + .iter() + .enumerate() + .map(|(index, token)| (token.start, TokenId(index))) + .collect(); + + Ok(Self { + tokens, + gaps, + comment_count, + token_by_offset, + }) + } + + pub(crate) fn token(&self, id: TokenId) -> &Token { + self.tokens + .get(id.index()) + .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())) + } + + pub(crate) fn gap(&self, boundary: TokenBoundary) -> &Gap { + self.gaps + .get(boundary.index()) + .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)) + .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 { + let _ = self.token(id); + TokenBoundary(id.index()) + } + + pub(crate) fn boundary_after(&self, id: TokenId) -> TokenBoundary { + let _ = self.token(id); + 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 { + let _ = self.token(id); + 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 { + self.token_by_offset.get(&offset).copied().ok_or_else(|| { + FormatError::Internal(format!( + "syntax token at source offset {offset} has no significant token ID" + )) + }) + } +} + +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')) +} + +#[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 new file mode 100644 index 00000000..9ee1516a --- /dev/null +++ b/crates/rue-formatter/src/trivia.rs @@ -0,0 +1,120 @@ +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)) +} + +/// 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)) +} 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-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-lsp/Cargo.toml b/crates/rue-lsp/Cargo.toml index b2728f47..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." @@ -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/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-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, + } + } +} 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-parser/src/grammar/expr.rs b/crates/rue-parser/src/grammar/expr.rs index dc77e16a..20abfc10 100644 --- a/crates/rue-parser/src/grammar/expr.rs +++ b/crates/rue-parser/src/grammar/expr.rs @@ -186,19 +186,9 @@ pub fn expr_with(p: &mut Parser, checkpoint: Checkpoint, options: ExprOptions) - return false; }; - let (left_binding_power, right_binding_power) = match op { - T![||] => (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/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::*; 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/crates/rue-tests/Cargo.toml b/crates/rue-tests/Cargo.toml index 47c0b332..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" @@ -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/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/examples/brainfck.rue b/examples/brainfck.rue index f6aa7740..ccd24c7f 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..a3dacc24 100644 --- a/examples/fizz_buzz.rue +++ b/examples/fizz_buzz.rue @@ -41,8 +41,20 @@ 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", + "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..c42157ad 100644 --- a/examples/puzzles/cat.rue +++ b/examples/puzzles/cat.rue @@ -83,7 +83,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], ) } @@ -100,16 +100,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 +121,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; @@ -160,7 +157,7 @@ fn main( lineage_proof, extra_delta, conditions, - morph.tail_info.tail_solution + morph.tail_info.tail_solution, ); // Prepend the TAIL's conditions. @@ -202,26 +199,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,7 +227,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..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: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: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/p2_delegated_conditions.rue b/examples/puzzles/p2_delegated_conditions.rue index f3c96e76..e98dba96 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..bd73e932 100644 --- a/examples/puzzles/partial_offer.rue +++ b/examples/puzzles/partial_offer.rue @@ -33,18 +33,21 @@ 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 +58,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 +81,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..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: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: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 922f4f2f..62fe4a8d 100644 --- a/examples/puzzles/singleton.rue +++ b/examples/puzzles/singleton.rue @@ -28,25 +28,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 +63,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 +103,29 @@ 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..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: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: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: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: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 diff --git a/tests/builtins.rue b/tests/builtins.rue index c802fb5d..1ae7b66a 100644 --- a/tests/builtins.rue +++ b/tests/builtins.rue @@ -12,12 +12,22 @@ test fn casting() { 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..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: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: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: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..f52ddc6a 100644 --- a/tests/expressions/blocks.rue +++ b/tests/expressions/blocks.rue @@ -9,11 +9,13 @@ test fn capture() -> Int { 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 +27,5 @@ test fn binding() -> Int { } test fn nested() -> Int { - {{{ 42 }}} + { { { 42 } } } } diff --git a/tests/expressions/if_else.rue b/tests/expressions/if_else.rue index 645779eb..253e2180 100644 --- a/tests/expressions/if_else.rue +++ b/tests/expressions/if_else.rue @@ -7,17 +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" } } 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..58fb06fe 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, diff --git a/tests/imports/duplicate_imports.rue b/tests/imports/duplicate_imports.rue index d13a5eed..0f966633 100644 --- a/tests/imports/duplicate_imports.rue +++ b/tests/imports/duplicate_imports.rue @@ -1,14 +1,14 @@ +import a::thing; + mod a { export fn thing() -> Int { 42 } - + export thing; 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 c17e463f..2fefa35d 100644 --- a/tests/imports/import_order.rue +++ b/tests/imports/import_order.rue @@ -1,3 +1,6 @@ +import a::hello_world_a; +import b::hello_world_b; + mod a { import b::hello; @@ -22,8 +25,62 @@ mod b { } } -import a::hello_world_a; -import b::hello_world_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/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 525f8732..c4a5396d 100644 --- a/tests/imports/unresolved_imports.rue +++ b/tests/imports/unresolved_imports.rue @@ -1,5 +1,5 @@ -mod module {} - -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 ba33c32e..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:4:16 -- Undeclared symbol `inner` at unresolved_imports.rue:5: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/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..a6445b58 100644 --- a/tests/projects/external_import/main.rue +++ b/tests/projects/external_import/main.rue @@ -1,10 +1,7 @@ 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"; +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 { 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..8a27ed2f 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() { diff --git a/tests/std.yaml b/tests/std.yaml index c5d0ed3f..2af2f4fa 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: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 diff --git a/tests/stress/many_params_binary_tree.rue b/tests/stress/many_params_binary_tree.rue index 83fca6b6..bfe9fbc8 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..6703503d 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/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/super_errors.rue b/tests/symbols/super_errors.rue index 8d5dfeb3..2da45d93 100644 --- a/tests/symbols/super_errors.rue +++ b/tests/symbols/super_errors.rue @@ -1,7 +1,7 @@ +import super; +import super::super; +import super::x; + mod a { import super::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..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: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 -- 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 -- Unused import `a` at super_errors.rue:2: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/types/struct_error.rue b/tests/types/struct_error.rue index 54389d2a..ae70f729 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..1d1739c2 100644 --- a/tests/warnings/unused_imports.rue +++ b/tests/warnings/unused_imports.rue @@ -1,3 +1,15 @@ +import deeply::nested; +import empty::*; +import glob::*; +import module::used; +import nested::module; +import private::*; +import shadow_1::*; +import shadow_1::c; +import shadow_2::*; +import shadow_2::d; +import single::a; + mod single { export fn a() -> Int { 42 @@ -30,15 +42,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 { @@ -49,10 +52,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 ce9c1be7..754d0139 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: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 `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 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 `*` at unused_imports.rue:2:15 +- Unused import `*` at unused_imports.rue:6:17 +- 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 +- 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 0f68a256..91c98c36 100644 --- a/tests/warnings/unused_symbols.rue +++ b/tests/warnings/unused_symbols.rue @@ -26,6 +26,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; fn used_by_main() {} diff --git a/tests/warnings/unused_symbols.yaml b/tests/warnings/unused_symbols.yaml index 03b5806e..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:29:7 -- Unused function `unused` at unused_symbols.rue:37: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 82733bb3..7b323822 100644 --- a/tests/warnings/unused_types.rue +++ b/tests/warnings/unused_types.rue @@ -1,8 +1,6 @@ type Recursive = Recursive; - type A = B; type B = A; - type Used = 42; type Unused = "Hello, world!"; @@ -19,7 +17,6 @@ struct UnusedStruct { type UsedAlias = UsedStruct; type UnusedAlias = UnusedStruct; - type Thing1 = 42; type Thing2 = 42; diff --git a/tests/warnings/unused_types.yaml b/tests/warnings/unused_types.yaml index 6a3c0450..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:10: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: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:5:6 +- Unused struct `UnusedStruct` at unused_types.rue:14:8 +- Unused type alias `UnusedAlias` at unused_types.rue:19:6 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"