From 23f69a33e5a4ba7aaa7bc9eadd857cb9359d1729 Mon Sep 17 00:00:00 2001 From: Charlotte Meyer Date: Fri, 21 Oct 2022 17:29:42 +0000 Subject: [PATCH] feat(builtins): use proc macro for builtins --- Cargo.lock | 102 ++++++++++++++++++-- crates/oyster_builtin_proc/Cargo.toml | 18 ++++ crates/oyster_builtin_proc/src/lib.rs | 53 ++++++++++ crates/oyster_builtin_proc/tests/it/main.rs | 10 ++ crates/oyster_runtime/Cargo.toml | 1 + crates/oyster_runtime/src/builtins/mod.rs | 27 +++--- crates/oyster_runtime/tests/it/pipeline.rs | 28 +++--- 7 files changed, 198 insertions(+), 41 deletions(-) create mode 100644 crates/oyster_builtin_proc/Cargo.toml create mode 100644 crates/oyster_builtin_proc/src/lib.rs create mode 100644 crates/oyster_builtin_proc/tests/it/main.rs diff --git a/Cargo.lock b/Cargo.lock index c1c9c7b..1dd56f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,18 +33,65 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "1.9.1" @@ -114,6 +161,18 @@ dependencies = [ "oyster_runtime", ] +[[package]] +name = "oyster_builtin_proc" +version = "0.0.0" +dependencies = [ + "darling", + "oyster_runtime", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "oyster_lineedit" version = "0.0.0" @@ -132,24 +191,36 @@ version = "0.0.0" dependencies = [ "insta", "nix", + "oyster_builtin_proc", "oyster_parser", "thiserror", ] [[package]] -name = "proc-macro2" -version = "1.0.40" +name = "proc-macro-crate" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -210,10 +281,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" [[package]] -name = "syn" -version = "1.0.98" +name = "strsim" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", @@ -250,6 +327,15 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "unicode-ident" version = "1.0.1" diff --git a/crates/oyster_builtin_proc/Cargo.toml b/crates/oyster_builtin_proc/Cargo.toml new file mode 100644 index 0000000..b12b648 --- /dev/null +++ b/crates/oyster_builtin_proc/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "oyster_builtin_proc" +version = "0.0.0" +edition = "2021" + +[lib] +doctest = false +proc-macro = true + +[dependencies] +darling = "~0.14.1" +proc-macro-crate = "^1.2.1" +proc-macro2 = { version = "1.0.47", default-features = false } +quote = { version = "^1.0.21", default-features = false } +syn = { version = "^1.0.102", default-features = false, features = [ "full" ] } + +[dev-dependencies] +oyster_runtime = { path = "../oyster_runtime" } diff --git a/crates/oyster_builtin_proc/src/lib.rs b/crates/oyster_builtin_proc/src/lib.rs new file mode 100644 index 0000000..8c403ed --- /dev/null +++ b/crates/oyster_builtin_proc/src/lib.rs @@ -0,0 +1,53 @@ +use darling::FromMeta; +use proc_macro::TokenStream; +use proc_macro2::Span; +use proc_macro_crate::{crate_name, FoundCrate}; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs, Ident, ItemFn, Signature}; + +#[derive(FromMeta)] +struct AttrArgs { + #[darling(default)] + description: String, +} + +#[proc_macro_attribute] +pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream { + let attrs = parse_macro_input!(attr as AttributeArgs); + let ItemFn { + vis, sig, block, .. + } = parse_macro_input!(item as ItemFn); + let Signature { + ident, + fn_token, + inputs, + .. + } = sig; + + let name = ident.to_string(); + let AttrArgs { description } = match AttrArgs::from_list(&attrs) { + Ok(args) => args, + Err(err) => return err.write_errors().into(), + }; + + let builtin = match crate_name("oyster_runtime").expect("oyster_runtime missing") { + FoundCrate::Itself => quote! { crate::builtins::Builtin }, + FoundCrate::Name(name) => { + let ident = Ident::new(&name, Span::call_site()); + quote! { #ident::builtins::Builtin } + } + }; + + TokenStream::from(quote! { + #[allow(non_upper_case_globals)] + #vis static #ident: #builtin = { + #fn_token #ident(#inputs) #block + + #builtin { + name: #name, + description: #description, + fun: #ident, + } + }; + }) +} diff --git a/crates/oyster_builtin_proc/tests/it/main.rs b/crates/oyster_builtin_proc/tests/it/main.rs new file mode 100644 index 0000000..f5f74aa --- /dev/null +++ b/crates/oyster_builtin_proc/tests/it/main.rs @@ -0,0 +1,10 @@ +use std::borrow::Cow; + +use oyster_builtin_proc::builtin; +use oyster_runtime::Shell; + +#[test] +fn normal_usage() { + #[builtin(description = "some text")] + fn test(_: &mut Shell, _: &[Cow]) {} +} diff --git a/crates/oyster_runtime/Cargo.toml b/crates/oyster_runtime/Cargo.toml index cf41047..bfe3499 100644 --- a/crates/oyster_runtime/Cargo.toml +++ b/crates/oyster_runtime/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" doctest = false [dependencies] +oyster_builtin_proc = { path = "../oyster_builtin_proc" } oyster_parser = { path = "../oyster_parser" } thiserror = "1.0.35" diff --git a/crates/oyster_runtime/src/builtins/mod.rs b/crates/oyster_runtime/src/builtins/mod.rs index a0ab658..6914e69 100644 --- a/crates/oyster_runtime/src/builtins/mod.rs +++ b/crates/oyster_runtime/src/builtins/mod.rs @@ -1,5 +1,7 @@ use std::{borrow::Cow, collections::HashMap}; +use oyster_builtin_proc::builtin; + use crate::Shell; #[derive(Clone, Copy)] @@ -9,25 +11,18 @@ pub struct Builtin { pub fun: fn(shell: &mut Shell, args: &[Cow]), } -pub static HELP: Builtin = { - pub fn help(shell: &mut Shell, _args: &[Cow]) { - println!( - r"oyster help: +#[builtin(description = "prints help for different builtins")] +fn help(shell: &mut Shell, _args: &[Cow]) { + println!( + r"oyster help: These are the loaded builtins:" - ); + ); - for builtin in shell.builtins().iter() { - println!(" {: <8} {}", builtin.name, builtin.description); - } + for builtin in shell.builtins().iter() { + println!(" {: <8} {}", builtin.name, builtin.description); } - - Builtin { - name: "help", - description: "prints help for different builtins", - fun: help, - } -}; +} /// Used to register and retrieve builtins. #[derive(Default)] @@ -51,7 +46,7 @@ impl BuiltinMap { /// Add default builtins. pub fn add_defaults(&mut self) { - self.add(HELP); + self.add(help); } } diff --git a/crates/oyster_runtime/tests/it/pipeline.rs b/crates/oyster_runtime/tests/it/pipeline.rs index c2669e4..99b9b73 100644 --- a/crates/oyster_runtime/tests/it/pipeline.rs +++ b/crates/oyster_runtime/tests/it/pipeline.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, env, fs::File, io::{BufRead, BufReader, Write}, @@ -13,12 +14,19 @@ use nix::{ sys, unistd::{self, ForkResult}, }; -use oyster_runtime::{builtins::Builtin, Shell}; +use oyster_builtin_proc::builtin; +use oyster_runtime::Shell; // TODO: test signal return codes ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY); +#[builtin(description = "test builtin")] +fn test_builtin(_: &mut Shell, _: &[Cow]) { + // XXX: this is a workaround around libtest's use of io::set_output_capture + let _ = write!(std::io::stdout(), "this is a test\n"); +} + /// Forks to redirect stdin, stderr, stdout, then run the commands. /// Relies on inserting a NUL byte in the end, so there shouldn't be NUL in the output. fn collect_output(mut f: F) -> String @@ -184,14 +192,7 @@ fn simple_builtin() { let actual = collect_output(|| { let mut shell = Shell::new().unwrap(); - shell.builtins_mut().add(Builtin { - name: "test_builtin", - description: "test", - fun: |_, _| { - // XXX: this is a workaround around libtest's use of io::set_output_capture - let _ = write!(std::io::stdout(), "this is a test\n"); - }, - }); + shell.builtins_mut().add(test_builtin); shell.run(&ast).unwrap(); }); @@ -220,14 +221,7 @@ fn builtin_redirection() { let actual = collect_output(|| { let mut shell = Shell::new().unwrap(); - shell.builtins_mut().add(Builtin { - name: "test_builtin", - description: "test", - fun: |_, _| { - // XXX: this is a workaround around libtest's use of io::set_output_capture - let _ = write!(std::io::stdout(), "this is a test\n"); - }, - }); + shell.builtins_mut().add(test_builtin); shell.run(&ast).unwrap(); });