feat(builtins): use proc macro for builtins

This commit is contained in:
buffet 2022-10-21 17:29:42 +00:00
parent bd23c3e40d
commit 23f69a33e5
7 changed files with 198 additions and 41 deletions

102
Cargo.lock generated
View file

@ -33,18 +33,65 @@ dependencies = [
"winapi", "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]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "0.3.6" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.1" version = "1.9.1"
@ -114,6 +161,18 @@ dependencies = [
"oyster_runtime", "oyster_runtime",
] ]
[[package]]
name = "oyster_builtin_proc"
version = "0.0.0"
dependencies = [
"darling",
"oyster_runtime",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "oyster_lineedit" name = "oyster_lineedit"
version = "0.0.0" version = "0.0.0"
@ -132,24 +191,36 @@ version = "0.0.0"
dependencies = [ dependencies = [
"insta", "insta",
"nix", "nix",
"oyster_builtin_proc",
"oyster_parser", "oyster_parser",
"thiserror", "thiserror",
] ]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro-crate"
version = "1.0.40" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.20" version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -210,10 +281,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
[[package]] [[package]]
name = "syn" name = "strsim"
version = "1.0.98" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -250,6 +327,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.1" version = "1.0.1"

View file

@ -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" }

View file

@ -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,
}
};
})
}

View file

@ -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<str>]) {}
}

View file

@ -7,6 +7,7 @@ edition = "2021"
doctest = false doctest = false
[dependencies] [dependencies]
oyster_builtin_proc = { path = "../oyster_builtin_proc" }
oyster_parser = { path = "../oyster_parser" } oyster_parser = { path = "../oyster_parser" }
thiserror = "1.0.35" thiserror = "1.0.35"

View file

@ -1,5 +1,7 @@
use std::{borrow::Cow, collections::HashMap}; use std::{borrow::Cow, collections::HashMap};
use oyster_builtin_proc::builtin;
use crate::Shell; use crate::Shell;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -9,8 +11,8 @@ pub struct Builtin {
pub fun: fn(shell: &mut Shell, args: &[Cow<str>]), pub fun: fn(shell: &mut Shell, args: &[Cow<str>]),
} }
pub static HELP: Builtin = { #[builtin(description = "prints help for different builtins")]
pub fn help(shell: &mut Shell, _args: &[Cow<str>]) { fn help(shell: &mut Shell, _args: &[Cow<str>]) {
println!( println!(
r"oyster help: r"oyster help:
@ -22,13 +24,6 @@ These are the loaded builtins:"
} }
} }
Builtin {
name: "help",
description: "prints help for different builtins",
fun: help,
}
};
/// Used to register and retrieve builtins. /// Used to register and retrieve builtins.
#[derive(Default)] #[derive(Default)]
pub struct BuiltinMap(HashMap<&'static str, Builtin>); pub struct BuiltinMap(HashMap<&'static str, Builtin>);
@ -51,7 +46,7 @@ impl BuiltinMap {
/// Add default builtins. /// Add default builtins.
pub fn add_defaults(&mut self) { pub fn add_defaults(&mut self) {
self.add(HELP); self.add(help);
} }
} }

View file

@ -1,4 +1,5 @@
use std::{ use std::{
borrow::Cow,
env, env,
fs::File, fs::File,
io::{BufRead, BufReader, Write}, io::{BufRead, BufReader, Write},
@ -13,12 +14,19 @@ use nix::{
sys, sys,
unistd::{self, ForkResult}, unistd::{self, ForkResult},
}; };
use oyster_runtime::{builtins::Builtin, Shell}; use oyster_builtin_proc::builtin;
use oyster_runtime::Shell;
// TODO: test signal return codes // TODO: test signal return codes
ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY); ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY);
#[builtin(description = "test builtin")]
fn test_builtin(_: &mut Shell, _: &[Cow<str>]) {
// 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. /// 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. /// Relies on inserting a NUL byte in the end, so there shouldn't be NUL in the output.
fn collect_output<F>(mut f: F) -> String fn collect_output<F>(mut f: F) -> String
@ -184,14 +192,7 @@ fn simple_builtin() {
let actual = collect_output(|| { let actual = collect_output(|| {
let mut shell = Shell::new().unwrap(); let mut shell = Shell::new().unwrap();
shell.builtins_mut().add(Builtin { shell.builtins_mut().add(test_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.run(&ast).unwrap(); shell.run(&ast).unwrap();
}); });
@ -220,14 +221,7 @@ fn builtin_redirection() {
let actual = collect_output(|| { let actual = collect_output(|| {
let mut shell = Shell::new().unwrap(); let mut shell = Shell::new().unwrap();
shell.builtins_mut().add(Builtin { shell.builtins_mut().add(test_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.run(&ast).unwrap(); shell.run(&ast).unwrap();
}); });