Skip to content

gskorokhod/polyquine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Polyquine: Teach Rust types to codegen their own constructor!

crates.io: polyquine

cargo add polyquine

Purpose

This crate contains:

  • A Quine trait. Types that implement Quine have a method ctor_tokens(&self) -> TokenStream; The tokens returned by it are valid Rust expression that, when evaluated, produces the original value.
  • Implementations for:
    • All primitives (i32, bool, etc.)
    • String
    • Fixed-size arrays [T; N]
    • Some std::collections types (Vec, HashMap, HashSet, etc.)
    • Tuples of up to 12 elements
    • Box<T>, Option<T>
  • Declarative macros to implement Quine for:
    • Iterables (derive_iterable). The given iterable must:
      • Have an .iter() method that returns an iterator over its elements.
      • Implement From<[T; N]>
  • A #[derive(Quine)] macro to derive the trait for enums and structs. All fields thereof must implement Quine.

For example:

#[derive(Quine)]
struct Node {
    value: i32,
    next: Option<Box<Node>>,
}

let node = Node {
    value: 1i32,
    next: Some(Box::new(Node {
        value: 2i32,
        next: None,
    })),
};
assert_ts_eq(
    &node.ctor_tokens(),
    // Evaluate this and you get the value of `node` back!
    &quote! {Node { value: 1i32, next: Some(Box::new(Node { value: 2i32, next: None })) }},
);

Attributes

You can supply a custom implementation for a specific variant only - handy when this variant contains a foreign type that does not implement Quine, or when you want some custom logic in its .ctor_tokens(). This is done using the attribute #[polyquine_with(arm = (val) => {...})].

#[derive(Quine)]
enum TestEnum {
    A,
    #[polyquine_with(arm = (val) => {
        let new_val = val + 1;
        quote! { TestEnum::B(#new_val) }
    })]
    B(i32),
}

let b = TestEnum::B(42i32);
assert_ts_eq(&b.ctor_tokens(), &quote! { TestEnum::B(43i32) }); // <- the value got incremented!

You can also skip variants using #[polyquine_skip]. By doing so, you pinky promise to never call .ctor_tokens() on an instance of that variant. If you do, it will panic!():

#[derive(Quine)]
enum TestEnum {
    A,
    #[polyquine_skip]
    B,
}

let a = TestEnum::A;
assert_ts_eq(
    &a.ctor_tokens(),
    &quote! {polyquine::quine::test::TestEnum::A},
); // All good for A

let b = TestEnum::B;
let _ = b.ctor_tokens(); // <- This should panic

Contributing

Contributions are always welcome!

  • If you need Quine to work with a specific type from std or a third-party crate, or have any other feature suggestions, open an issue tagged "feature request".
  • If you encounter any bugs / unexpected behaviour, open an issue. Please include the error message and relevant code snippets.
  • Pull requests are very much appreciated! I will do my best to review them ASAP. When submitting a patch, please:
    • Make sure that the linter is happy and all tests pass:
      cargo fmt
      cargo clippy --workspace --fix
      cargo test --workspace
      
    • Include a short message explaining the changes and reasoning behind them

See the issues for planned feature tracking and known bugs

Notes

  • Version 0.0.2 is a complete rewrite. We now use our own trait instead of deriving quote::ToTokens. All field attributes from version 0.0.1 are no longer supported.
  • We ignore references, i.e. the implementation for &T is the same as for T. This may cause issues - please open an issue if you encounter any.
  • Please open an issue if any std type is not supported and you need it.
  • Rc, Arc and friends are unlikely to ever be supported. This is due to their shared ownership semantics: We can have multiple Rc's pointing to the same memory. When implementing Quine, the best we can do is re-construct their values separately. When evaluated, we will now have multiple independent copies of the original value - not a single value behind a shared reference.

Acknowledgements

Partially inspired by the parsel crate.

Why?

This is intended for cases where you need to construct a value and do some non-trivial logic on it at compile time (probably in a procedural macro), then take the result out of the macro as valid Rust code.

For example, when you are parsing a DSL at compile time and outputting the constructed (and possibly simplified / transformed) AST as the result of your macro call.

See this PR as proof that this use case is not completely made up.

Production readiness

still no.

Why the name?

A quine is a program that outputs its own source code. This crate teaches an arbitrary enum / struct to generate a Rust program that constructs it, thereby making it a quine. So, polyquine! This is only slightly pretentious.


also, trans rights! 🏳️‍⚧️

About

Teach Rust types to codegen their own constructors

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages