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:
buffet 2022-11-05 12:28:55 +00:00
parent ac8a22ad99
commit 5e18e89af4
5 changed files with 244 additions and 1 deletions

View file

@ -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) => {

View 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);

View file

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

View 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"
"#]],
);
}

View file

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