feat(builtins): add basic math builtins
This adds basic addition, substraction, multiplication, and division commands as builtins. All of those operate solely on f64's. Signed-off-by: Charlotte Meyer <dev@buffet.sh>
This commit is contained in:
parent
ac8a22ad99
commit
5e18e89af4
5 changed files with 244 additions and 1 deletions
|
@ -7,6 +7,7 @@ use syn::{parse_macro_input, AttributeArgs, Ident, ItemFn, Signature};
|
||||||
|
|
||||||
#[derive(FromMeta)]
|
#[derive(FromMeta)]
|
||||||
struct AttrArgs {
|
struct AttrArgs {
|
||||||
|
name: Option<String>,
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
description: String,
|
description: String,
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
|
@ -26,8 +27,8 @@ pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
..
|
..
|
||||||
} = sig;
|
} = sig;
|
||||||
|
|
||||||
let name = ident.to_string();
|
|
||||||
let AttrArgs {
|
let AttrArgs {
|
||||||
|
name,
|
||||||
description,
|
description,
|
||||||
nofork,
|
nofork,
|
||||||
} = match AttrArgs::from_list(&attrs) {
|
} = match AttrArgs::from_list(&attrs) {
|
||||||
|
@ -35,6 +36,8 @@ pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
Err(err) => return err.write_errors().into(),
|
Err(err) => return err.write_errors().into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let name = name.unwrap_or_else(|| ident.to_string());
|
||||||
|
|
||||||
let builtin = match crate_name("oyster_runtime").expect("oyster_runtime missing") {
|
let builtin = match crate_name("oyster_runtime").expect("oyster_runtime missing") {
|
||||||
FoundCrate::Itself => quote! { crate::builtins::Builtin },
|
FoundCrate::Itself => quote! { crate::builtins::Builtin },
|
||||||
FoundCrate::Name(name) => {
|
FoundCrate::Name(name) => {
|
||||||
|
|
57
crates/oyster_runtime/src/builtins/math.rs
Normal file
57
crates/oyster_runtime/src/builtins/math.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
ffi::OsStr,
|
||||||
|
io::{self, Write},
|
||||||
|
process,
|
||||||
|
};
|
||||||
|
|
||||||
|
use oyster_builtin_proc::builtin;
|
||||||
|
|
||||||
|
use crate::Shell;
|
||||||
|
|
||||||
|
macro_rules! operator {
|
||||||
|
($name:literal, $desc:literal, $funcname:ident, $operator:tt, $neutral:literal) => {
|
||||||
|
#[builtin(name = $name, description = $desc)]
|
||||||
|
pub fn $funcname(_shell: &mut Shell, args: &[Cow<OsStr>]) {
|
||||||
|
let mut errored = false;
|
||||||
|
|
||||||
|
let mut parse = |n: &Cow<OsStr>| {
|
||||||
|
n
|
||||||
|
.to_str()
|
||||||
|
.map(|n| {
|
||||||
|
n.parse::<f64>().unwrap_or_else(|_| {
|
||||||
|
writeln!(io::stderr(), "not a number: {:?}", n).unwrap();
|
||||||
|
errored = true;
|
||||||
|
$neutral
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
writeln!(io::stderr(), "not a number: {:?}", n).unwrap();
|
||||||
|
errored = true;
|
||||||
|
$neutral
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut args = args.iter();
|
||||||
|
let mut acc = args.next().map(&mut parse).unwrap_or($neutral);
|
||||||
|
|
||||||
|
#[allow(clippy::assign_op_pattern)]
|
||||||
|
for n in args {
|
||||||
|
acc = acc $operator parse(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(io::stdout(), "{}", acc).unwrap();
|
||||||
|
|
||||||
|
process::exit(if errored {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator!("+", "adds numbers", add, +, 0.0);
|
||||||
|
operator!("-", "subtracts numbers", sub, -, 0.0);
|
||||||
|
operator!("*", "multiplies numbers", mul, *, 1.0);
|
||||||
|
operator!("/", "divides numbers", div, /, 1.0);
|
|
@ -1,3 +1,5 @@
|
||||||
|
pub mod math;
|
||||||
|
|
||||||
use std::{borrow::Cow, collections::HashMap, ffi::OsStr};
|
use std::{borrow::Cow, collections::HashMap, ffi::OsStr};
|
||||||
|
|
||||||
use oyster_builtin_proc::builtin;
|
use oyster_builtin_proc::builtin;
|
||||||
|
@ -55,6 +57,10 @@ impl BuiltinMap {
|
||||||
pub fn add_defaults(&mut self) {
|
pub fn add_defaults(&mut self) {
|
||||||
self.add(exit);
|
self.add(exit);
|
||||||
self.add(help);
|
self.add(help);
|
||||||
|
self.add(math::add);
|
||||||
|
self.add(math::sub);
|
||||||
|
self.add(math::mul);
|
||||||
|
self.add(math::div);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
175
crates/oyster_runtime/tests/it/builtins/math.rs
Normal file
175
crates/oyster_runtime/tests/it/builtins/math.rs
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
use expect_test::{expect, Expect};
|
||||||
|
use oyster_parser::ast;
|
||||||
|
use oyster_runtime::Shell;
|
||||||
|
|
||||||
|
use crate::collect_output;
|
||||||
|
|
||||||
|
fn check(ast: &ast::Code, expect: Expect) {
|
||||||
|
let actual = collect_output(|| {
|
||||||
|
let mut shell = Shell::new().unwrap();
|
||||||
|
shell.builtins_mut().add_defaults();
|
||||||
|
shell.run(ast).unwrap();
|
||||||
|
});
|
||||||
|
expect.assert_debug_eq(&actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_single() {
|
||||||
|
let ast = {
|
||||||
|
use oyster_parser::ast::*;
|
||||||
|
|
||||||
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
||||||
|
vec![
|
||||||
|
Word(vec![WordPart::Text("+")]),
|
||||||
|
Word(vec![WordPart::Text("5")]),
|
||||||
|
],
|
||||||
|
Redirect::None,
|
||||||
|
)]))])
|
||||||
|
};
|
||||||
|
|
||||||
|
check(
|
||||||
|
&ast,
|
||||||
|
expect![[r#"
|
||||||
|
"5\r\n"
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_multiple() {
|
||||||
|
let ast = {
|
||||||
|
use oyster_parser::ast::*;
|
||||||
|
|
||||||
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
||||||
|
vec![
|
||||||
|
Word(vec![WordPart::Text("+")]),
|
||||||
|
Word(vec![WordPart::Text("5")]),
|
||||||
|
Word(vec![WordPart::Text("2")]),
|
||||||
|
Word(vec![WordPart::Text("3")]),
|
||||||
|
],
|
||||||
|
Redirect::None,
|
||||||
|
)]))])
|
||||||
|
};
|
||||||
|
|
||||||
|
check(
|
||||||
|
&ast,
|
||||||
|
expect![[r#"
|
||||||
|
"10\r\n"
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sub() {
|
||||||
|
let ast = {
|
||||||
|
use oyster_parser::ast::*;
|
||||||
|
|
||||||
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
||||||
|
vec![
|
||||||
|
Word(vec![WordPart::Text("-")]),
|
||||||
|
Word(vec![WordPart::Text("5")]),
|
||||||
|
Word(vec![WordPart::Text("2")]),
|
||||||
|
],
|
||||||
|
Redirect::None,
|
||||||
|
)]))])
|
||||||
|
};
|
||||||
|
|
||||||
|
check(
|
||||||
|
&ast,
|
||||||
|
expect![[r#"
|
||||||
|
"3\r\n"
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mul() {
|
||||||
|
let ast = {
|
||||||
|
use oyster_parser::ast::*;
|
||||||
|
|
||||||
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
||||||
|
vec![
|
||||||
|
Word(vec![WordPart::Text("*")]),
|
||||||
|
Word(vec![WordPart::Text("5")]),
|
||||||
|
Word(vec![WordPart::Text("2")]),
|
||||||
|
],
|
||||||
|
Redirect::None,
|
||||||
|
)]))])
|
||||||
|
};
|
||||||
|
|
||||||
|
check(
|
||||||
|
&ast,
|
||||||
|
expect![[r#"
|
||||||
|
"10\r\n"
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_div() {
|
||||||
|
let ast = {
|
||||||
|
use oyster_parser::ast::*;
|
||||||
|
|
||||||
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
||||||
|
vec![
|
||||||
|
Word(vec![WordPart::Text("/")]),
|
||||||
|
Word(vec![WordPart::Text("10")]),
|
||||||
|
Word(vec![WordPart::Text("2")]),
|
||||||
|
],
|
||||||
|
Redirect::None,
|
||||||
|
)]))])
|
||||||
|
};
|
||||||
|
|
||||||
|
check(
|
||||||
|
&ast,
|
||||||
|
expect![[r#"
|
||||||
|
"5\r\n"
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fraction_div() {
|
||||||
|
let ast = {
|
||||||
|
use oyster_parser::ast::*;
|
||||||
|
|
||||||
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
||||||
|
vec![
|
||||||
|
Word(vec![WordPart::Text("/")]),
|
||||||
|
Word(vec![WordPart::Text("9")]),
|
||||||
|
Word(vec![WordPart::Text("2")]),
|
||||||
|
],
|
||||||
|
Redirect::None,
|
||||||
|
)]))])
|
||||||
|
};
|
||||||
|
|
||||||
|
check(
|
||||||
|
&ast,
|
||||||
|
expect![[r#"
|
||||||
|
"4.5\r\n"
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn div_inf() {
|
||||||
|
let ast = {
|
||||||
|
use oyster_parser::ast::*;
|
||||||
|
|
||||||
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
||||||
|
vec![
|
||||||
|
Word(vec![WordPart::Text("/")]),
|
||||||
|
Word(vec![WordPart::Text("1")]),
|
||||||
|
Word(vec![WordPart::Text("0")]),
|
||||||
|
],
|
||||||
|
Redirect::None,
|
||||||
|
)]))])
|
||||||
|
};
|
||||||
|
|
||||||
|
check(
|
||||||
|
&ast,
|
||||||
|
expect![[r#"
|
||||||
|
"inf\r\n"
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
mod math;
|
||||||
|
|
||||||
use std::{borrow::Cow, ffi::OsStr, io::Write};
|
use std::{borrow::Cow, ffi::OsStr, io::Write};
|
||||||
|
|
||||||
use expect_test::{expect, Expect};
|
use expect_test::{expect, Expect};
|
||||||
|
|
Loading…
Reference in a new issue