diff --git a/rr_frontend/Cargo.lock b/rr_frontend/Cargo.lock index dd573b9880d008a4ef9c7a5674c620272d2c145b..f9e207be4fb2c2aa54835c975d5aaf3cf6f2c632 100644 --- a/rr_frontend/Cargo.lock +++ b/rr_frontend/Cargo.lock @@ -133,12 +133,58 @@ dependencies = [ "memchr", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "datafrog" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0afaad2b26fa326569eb264b1363e8ae3357618c43982b3f285f0774ce76b69" +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -221,6 +267,33 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "from_variants" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e859c8f2057687618905dbe99fc76e836e0a69738865ef90e46fc214a41bbf2" +dependencies = [ + "from_variants_impl", +] + +[[package]] +name = "from_variants_impl" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a5e644a80e6d96b2b4910fa7993301d7b7926c045b475b62202b20a36ce69e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -239,6 +312,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indent_write" version = "2.2.0" @@ -394,7 +473,9 @@ dependencies = [ name = "radium" version = "0.1.0" dependencies = [ + "derive-new", "derive_more 1.0.0-beta.6", + "from_variants", "indent_write", "indoc", "itertools", @@ -532,6 +613,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" diff --git a/rr_frontend/Cargo.toml b/rr_frontend/Cargo.toml index ec8d9f9ef3c27a60c28706a6e911fe5a51bda4bf..fe1903586565a5c8eb8af2a7a60c4dd8b681dbd6 100644 --- a/rr_frontend/Cargo.toml +++ b/rr_frontend/Cargo.toml @@ -21,8 +21,10 @@ analysis = { git = "https://github.com/viperproject/prusti-dev.git", rev = "24bd config = { version = "0.14", default-features = false, features = ["toml"] } csv = "1" datafrog = "2" -derive_more = { version = "1.0.0-beta.6", features = ["constructor", "debug", "deref", "display"] } +derive-new = "0.5" +derive_more = { version = "1.0.0-beta.6", features = ["constructor", "debug", "deref", "deref_mut", "display"] } env_logger = { version = "0.11" } +from_variants = "1" glob = "0.3" indent_write = "2" indoc = "2" diff --git a/rr_frontend/radium/Cargo.toml b/rr_frontend/radium/Cargo.toml index 2849d38c3259270ee2f8287dd1a19e8f66481eef..67a7cf8caf2d727408cd358282797ccd2ef15ce3 100644 --- a/rr_frontend/radium/Cargo.toml +++ b/rr_frontend/radium/Cargo.toml @@ -8,9 +8,11 @@ repository.workspace = true version.workspace = true [dependencies] -typed-arena.workspace = true +derive-new.workspace = true derive_more.workspace = true +from_variants.workspace = true indent_write.workspace = true indoc.workspace = true log.workspace = true itertools.workspace = true +typed-arena.workspace = true diff --git a/rr_frontend/radium/src/coq/mod.rs b/rr_frontend/radium/src/coq/mod.rs index 4ada12f13703ce103361592702cfba3bf0b93563..6bc9590c55177761f5c4f46bbd48cc386a53a4db 100644 --- a/rr_frontend/radium/src/coq/mod.rs +++ b/rr_frontend/radium/src/coq/mod.rs @@ -4,16 +4,411 @@ // If a copy of the BSD-3-clause license was not distributed with this // file, You can obtain one at https://opensource.org/license/bsd-3-clause/. -/// A collection of types to represent and generate [Rocq] code. -/// -/// These types are intended to be used for the purposes of the [RefinedRust project]. -/// As such, they may not be suitable for general use. -/// -/// This crate is split up in the same way as the [Rocq reference documentation]. -/// -/// [RefinedRust project]: https://plv.mpi-sws.org/refinedrust/ -/// [Rocq]: https://coq.inria.fr -/// [Rocq reference]: https://coq.inria.fr/doc/master/refman/index.html +//! A collection of types for representing and generating [Rocq] code. +//! +//! These types are intended to be used for the purposes of the [RefinedRust project]. +//! As such, they may not be suitable for general use. +//! +//! --- +//! +//! This crate is divided in a similar way to the grammar defined in the [Rocq reference] documentation, but +//! is not followed strictly: +//! - Unstructured sentences have been reunited under a single structure, such as `Section`/`End`. +//! - Some cases are unified for simplicity, such as `command`/`control_command`/`query_command`. +//! +//! The syntax used to create structures is designed to write as little code as possible, with choices +//! explained in the Notes section. +//! +//! --- +//! +//! Following the [Rocq reference], most of the types can be structured as nested enumerations. +//! However, some types can take optional attributes that will be left empty most of the time: +//! - Structures wrapping the element are required, suffixed with `Attrs`. +//! - Consuming builder pattern, without any validation, is used to fill the attributes. +//! +//! --- +//! +//! Each enumeration derives the `From` trait for each variant, and each element wrapped in an `Attr` +//! structure are coerced into the parent enumeration using the same `From` trait. +//! +//! This concept makes sense when each arguments expects a type that can be coerced into the final expected +//! type, allowing the user not to explicitly write `.into()` everywhere. +//! +//! # Examples +//! +//! Let's write the following [Rocq] code: +//! ```text +//! From Coq.Strings Require Import String. +//! Open Scope string_scope. +//! Compute "Hello World". +//! ``` +//! +//! This code can be generated with the following Rust code: +//! ``` +//! # use radium::coq::*; +//! // Document(vec![ +//! // TODO +//! // ]); +//! ``` +//! +//! This code can be reduced using coercions in the following Rust code: +//! ``` +//! # use radium::coq::*; +//! // Document::new(vec![ +//! // TODO +//! // ]); +//! ``` +//! +//! # Notes +//! +//! This section explains the various design decisions that have been made. +//! It also allows a developer to understand how to contribute to this crate. +//! +//! Throughout this section, let's take the following example: +//! ``` +//! struct FieldAttrs { +//! field: Field, +//! attributes: Option<String>, +//! }; +//! +//! enum Field { +//! Foo, +//! Bar, +//! }; +//! ``` +//! +//! ## Filling the attributes +//! +//! The following idiomatic code could be used, but it is too verbose as each field must be filled. +//! Also, this syntax is not resilient to future changes in the structure of types: +//! ``` +//! # struct FieldAttrs { field: Field, attributes: Option<String> }; +//! # enum Field { Foo, Bar }; +//! FieldAttrs { +//! field: Field::Foo, +//! attributes: None, +//! }; +//! ``` +//! +//! The following idiomatic syntax cannot be used because some types contain an enumeration: +//! ``` compile_fail +//! # struct FieldAttrs { field: Field, attributes: Option<String> }; +//! # enum Field { Foo, Bar }; +//! FieldAttrs { +//! field: Field::Foo, +//! ..FieldAttrs::default() // Error: `Field` is an enumeration without a default value. +//! }; +//! ``` +//! +//! Instead, the following builder pattern is preferred where mandatory fields are filled on creation, and +//! optional fields are filled with a method call: +//! ``` +//! # struct FieldAttrs { field: Field, attributes: Option<String> }; +//! # enum Field { Foo, Bar }; +//! impl FieldAttrs { +//! fn new(field: Field) -> Self { +//! let attributes = None; +//! +//! Self { field, attributes } +//! } +//! +//! fn attributes(self, attributes: String) -> Self { +//! let attributes = Some(attributes); +//! +//! Self { attributes, ..self } +//! } +//! } +//! +//! FieldAttrs::new(Field::Foo).attributes("Hello".to_string()); +//! ``` +//! +//! There are several builder patterns available, but owned (_aka consuming_) builder pattern has been chosen: +//! - It is possible to chain method calls to keep everything concise. +//! - There is no need to `.copy()`/`.clone()` objects to build the final object. +//! - No verification is performed, which would be unmaintainable and currently outside the scope of this +//! crate. +//! - Therefore, there is no need to `.build()?` the object, as it can be considered built after each step. +//! +//! ## Type coercions +//! +//! Let's take the following code, which is simple but already verbose: +//! ``` +//! # struct FieldAttrs { field: Field, attributes: Option<String> }; +//! # enum Field { Foo, Bar }; +//! # impl FieldAttrs { +//! # fn new(field: Field) -> Self { Self { field, attributes: None } } +//! # fn attributes(self, attributes: String) -> Self { Self { attributes: Some(attributes), ..self } } +//! # } +//! use derive_more::{Deref, DerefMut}; +//! +//! #[derive(Deref, DerefMut)] +//! struct Entries(Vec<Entry>); +//! +//! enum Entry { +//! FieldAttrs(FieldAttrs), +//! }; +//! +//! Entries(vec![ +//! Entry::FieldAttrs(FieldAttrs::new(Field::Foo)), +//! Entry::FieldAttrs(FieldAttrs::new(Field::Bar).attributes("Hello".to_string())), +//! ]); +//! ``` +//! +//! Writing the attribute structure can be unnecessarily verbose if no attributes are given. +//! +//! To avoid this, the `From<Field>` trait is derived for `Entry`, resulting in the following syntax: +//! ``` +//! # struct FieldAttrs { field: Field, attributes: Option<String> }; +//! # enum Field { Foo, Bar }; +//! # impl FieldAttrs { +//! # fn new(field: Field) -> Self { Self { field, attributes: None } } +//! # fn attributes(self, attributes: String) -> Self { Self { attributes: Some(attributes), ..self } } +//! # } +//! use derive_more::{Deref, DerefMut}; +//! +//! #[derive(Deref, DerefMut)] +//! struct Entries(Vec<Entry>); +//! +//! enum Entry { +//! FieldAttrs(FieldAttrs), +//! }; +//! +//! impl From<Field> for Entry { +//! fn from(field: Field) -> Self { +//! Self::FieldAttrs(FieldAttrs::new(field)) +//! } +//! } +//! +//! Entries(vec![ +//! Field::Foo.into(), +//! Entry::FieldAttrs(FieldAttrs::new(Field::Bar).attributes("Hello".to_string())), +//! ]); +//! ``` +//! +//! Similarly, the `.into()` method can be derived for each enumeration variant using `FromVariant`: +//! ``` +//! # struct FieldAttrs { field: Field, attributes: Option<String> }; +//! # enum Field { Foo, Bar }; +//! # impl FieldAttrs { +//! # fn new(field: Field) -> Self { Self { field, attributes: None } } +//! # fn attributes(self, attributes: String) -> Self { Self { attributes: Some(attributes), ..self } } +//! # } +//! # impl From<Field> for Entry { +//! # fn from(field: Field) -> Self { Self::FieldAttrs(FieldAttrs::new(field)) } +//! # } +//! use derive_more::{Deref, DerefMut}; +//! use from_variants::FromVariants; +//! +//! #[derive(Deref, DerefMut)] +//! struct Entries(Vec<Entry>); +//! +//! #[derive(FromVariants)] +//! enum Entry { +//! FieldAttrs(FieldAttrs), +//! }; +//! +//! Entries(vec![ +//! Field::Foo.into(), +//! FieldAttrs::new(Field::Bar).attributes("Hello".to_string()).into(), +//! ]); +//! ``` +//! +//! ## Call-site into +//! +//! With this syntax, the caller will be filled with lots of `.into()`. +//! +//! This can be circumvented with function argument types. Instead of taking `T`, functions should take `impl +//! Into<T>`. +//! +//! <section class="warning"> +//! +//! Although concise for user writing, this approach can be seen as a bad practice as monomorphising for each +//! `T` duplicates the function's body. In practice, the methods are tiny and easily inlineable by the +//! compiler. +//! +//! </section> +//! +//! Then, the previous example can be written using this syntax: +//! ``` +//! # use from_variants::FromVariants; +//! # struct Entries(Vec<Entry>); +//! # #[derive(FromVariants)] enum Entry { FieldAttrs(FieldAttrs) }; +//! # struct FieldAttrs { field: Field, attributes: Option<String> }; +//! # impl FieldAttrs { +//! # fn new(field: Field) -> Self { Self { field, attributes: None } } +//! # } +//! # enum Field { Foo, Bar }; +//! # impl From<Field> for Entry { +//! # fn from(field: Field) -> Self { Self::FieldAttrs(FieldAttrs::new(field)) } +//! # } +//! impl Entries { +//! pub fn push(mut self, entry: impl Into<Entry>) -> Self { +//! self.0.push(entry.into()); +//! self +//! } +//! } +//! +//! impl FieldAttrs { +//! fn attributes(self, attributes: impl Into<String>) -> Self { +//! let attributes = Some(attributes.into()); +//! Self { attributes, ..self } +//! } +//! } +//! +//! Entries(vec![]).push(Field::Foo).push(FieldAttrs::new(Field::Bar).attributes("Hello")); +//! ``` +//! +//! <section class="warning"> +//! +//! Heteregeneous does not exist in Rust, and coercion does not apply in the caller. +//! Therefore, the example cannot merge `Field` and `FieldAttrs`, and must use `.push()` instead. +//! +//! However, if there is a homogeneous list, an `impl Into` argument can be used: +//! ``` +//! # use from_variants::FromVariants; +//! # struct Entries(Vec<Entry>); +//! # #[derive(FromVariants)] enum Entry { FieldAttrs(FieldAttrs) }; +//! # struct FieldAttrs { field: Field, attributes: Option<String> }; +//! # enum Field { Foo, Bar }; +//! # impl FieldAttrs { +//! # fn new(field: Field) -> Self { Self { field, attributes: None } } +//! # } +//! # impl From<Field> for Entry { +//! # fn from(field: Field) -> Self { Self::FieldAttrs(FieldAttrs::new(field)) } +//! # } +//! impl Entries { +//! fn new(entries: Vec<impl Into<Entry>>) -> Self { +//! Self(entries.into_iter().map(Into::into).collect()) +//! } +//! } +//! +//! Entries::new(vec![Field::Foo, Field::Bar]); +//! ``` +//! +//! In practice, this case is not expected to happen very often. +//! +//! </section> +//! +//! [RefinedRust project]: https://plv.mpi-sws.org/refinedrust/ +//! [Rocq]: https://coq.inria.fr +//! [Rocq reference]: https://coq.inria.fr/doc/master/refman/index.html +//! [sections]: https://coq.inria.fr/doc/master/refman/language/core/sections.html + pub mod command; pub mod module; pub mod term; + +use std::fmt; + +use derive_more::{Deref, DerefMut, Display}; +use derive_new::new; +use from_variants::FromVariants; +use indoc::writedoc; + +use crate::write_list; + +/// A [document] is the entry-point to create a script, composed of [sentences]. +/// +/// [commands]: https://coq.inria.fr/doc/master/refman/language/core/basic.html#grammar-token-command +/// [document]: https://coq.inria.fr/doc/master/refman/language/core/basic.html#grammar-token-document +/// [sentences]: https://coq.inria.fr/doc/master/refman/language/core/basic.html#grammar-token-sentence +#[derive(Eq, PartialEq, Debug, Default, Deref, DerefMut)] +pub struct Document(pub Vec<Sentence>); + +impl Document { + #[must_use] + pub fn new(sentences: Vec<impl Into<Sentence>>) -> Self { + Self(sentences.into_iter().map(Into::into).collect()) + } + + pub fn push(&mut self, sentence: impl Into<Sentence>) { + self.0.push(sentence.into()); + } +} + +/// Beware, imports and exports [Command] are gathered on top of the list. +#[derive(Eq, PartialEq, Debug, FromVariants)] +pub enum Sentence { + CommandAttrs(CommandAttrs), +} + +#[derive(Eq, PartialEq, Debug)] +pub struct CommandAttrs { + pub command: Command, + pub attributes: Option<String>, +} + +impl CommandAttrs { + #[must_use] + pub fn new(command: impl Into<Command>) -> Self { + Self { + attributes: None, + command: command.into(), + } + } + + #[must_use] + pub fn attributes(self, attributes: impl Into<String>) -> Self { + let attributes = Some(attributes.into()); + + Self { attributes, ..self } + } +} + +/// A [command], the entrypoint of everything that can be written. +/// +/// [command]: https://coq.inria.fr/doc/master/refman/language/core/basic.html#grammar-token-command +#[derive(Eq, PartialEq, Debug, FromVariants)] +pub enum Command { + Require(Require), + + Proof, + // TODO: Add section, ... +} + +impl From<Command> for Sentence { + fn from(command: Command) -> Self { + Self::CommandAttrs(CommandAttrs::new(command)) + } +} + +#[derive(Eq, PartialEq, Debug)] +pub struct Require { + pub from: Option<String>, + pub import: Vec<String>, +} + +impl Require { + #[must_use] + pub fn new(import: Vec<impl Into<String>>) -> Self { + Self { + from: None, + import: import.into_iter().map(Into::into).collect(), + } + } + + #[must_use] + pub fn from(self, from: impl Into<String>) -> Self { + let from = Some(from.into()); + + Self { from, ..self } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_document() { + let mut doc = Document::new(vec![ + Command::Require(Require::new(vec!["nat", "bool"]).from("Coq.Init.Datatypes")), + Command::Require(Require::new(vec!["nat", "bool"]).from("Coq.Init.Datatypes")), + Command::Proof, + ]); + + doc.push(Command::Require(Require::new(vec!["nat", "bool"]).from("Coq.Init.Datatypes"))); + doc.push(Command::Proof); + doc.push(CommandAttrs::new(Command::Proof).attributes("Some")); + } +}