You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
meli/meli/src/plugins/starlark.rs

412 lines
12 KiB
Rust

/*
* ____
*
* Copyright ____ Manos Pitsidianakis
*
* This file is part of ____.
*
* ____ is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ____ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ____. If not, see <http://www.gnu.org/licenses/>.
*/
#[cfg(test)]
#[test]
fn test_starlark() {
use starlark::{
environment::{Globals, Module},
eval::Evaluator,
syntax::{AstModule, Dialect},
values::Value,
};
let content = r#"
def hello():
return "hello"
hello() + " world!"
"#;
// We first parse the content, giving a filename and the Starlark
// `Dialect` we'd like to use (we pick standard).
let ast: AstModule =
AstModule::parse("hello_world.star", content.to_owned(), &Dialect::Standard).unwrap();
// We create a `Globals`, defining the standard library functions available.
// The `standard` function uses those defined in the Starlark specification.
let globals: Globals = Globals::standard();
// We create a `Module`, which stores the global variables for our calculation.
let module: Module = Module::new();
// We create an evaluator, which controls how evaluation occurs.
let mut eval: Evaluator = Evaluator::new(&module);
// And finally we evaluate the code using the evaluator.
let res: Value = eval.eval_module(ast, &globals).unwrap();
assert_eq!(res.unpack_str(), Some("hello world!"));
}
#[cfg(test)]
#[test]
fn test_starlark_2() {
use starlark::{
environment::{GlobalsBuilder, Module},
eval::Evaluator,
starlark_module,
syntax::{AstModule, Dialect},
};
// This defines the function that is visible to Starlark
#[starlark_module]
fn starlark_quadratic(builder: &mut GlobalsBuilder) {
fn quadratic(a: i32, b: i32, c: i32, x: i32) -> starlark::Result<i32> {
Ok(a * x * x + b * x + c)
}
}
// We build our globals to make the function available in Starlark
let globals = GlobalsBuilder::new().with(starlark_quadratic).build();
let module = Module::new();
let mut eval = Evaluator::new(&module);
// Let's test calling the function from Starlark code
let starlark_code = r#"
quadratic(4, 2, 1, x = 8)
"#;
let ast = AstModule::parse(
"quadratic.star",
starlark_code.to_owned(),
&Dialect::Standard,
)
.unwrap();
let res = eval.eval_module(ast, &globals).unwrap();
assert_eq!(res.unpack_i32(), Some(273)); // Verify that we got an `int`
// return value of 4 * 8^2 + 2 * 8
// + 1 = 273
}
#[cfg(test)]
#[test]
fn test_starlark_3() {
use std::cell::RefCell;
use starlark::{
any::ProvidesStaticType,
environment::{GlobalsBuilder, Module},
eval::Evaluator,
starlark_module,
syntax::{AstModule, Dialect},
values::{none::NoneType, Value},
};
let content = r#"
emit(1)
emit(["test"])
emit({"x": "y"})
"#;
// Define a store in which to accumulate JSON strings
#[derive(Debug, ProvidesStaticType, Default)]
struct Store(RefCell<Vec<String>>);
impl Store {
fn add(&self, x: String) {
self.0.borrow_mut().push(x)
}
}
#[starlark_module]
fn starlark_emit(builder: &mut GlobalsBuilder) {
fn emit(x: Value, eval: &mut Evaluator) -> starlark::Result<NoneType> {
// We modify extra (which we know is a Store) and add the JSON of the
// value the user gave.
eval.extra
.unwrap()
.downcast_ref::<Store>()
.unwrap()
.add(x.to_json()?);
Ok(NoneType)
}
}
let ast = AstModule::parse("json.star", content.to_owned(), &Dialect::Standard).unwrap();
// We build our globals adding some functions we wrote
let globals = GlobalsBuilder::new().with(starlark_emit).build();
let module = Module::new();
let store = Store::default();
{
let mut eval = Evaluator::new(&module);
// We add a reference to our store
eval.extra = Some(&store);
eval.eval_module(ast, &globals).unwrap();
}
assert_eq!(&*store.0.borrow(), &["1", "[\"test\"]", "{\"x\":\"y\"}"]);
}
#[cfg(test)]
#[test]
fn test_starlark_4() {
use starlark::{
environment::{Globals, Module},
eval::Evaluator,
syntax::{AstModule, Dialect, DialectTypes},
};
let content = r#"
def takes_int(x: int):
pass
takes_int("test")
"#;
// Make the dialect enable types
let dialect = Dialect {
enable_types: DialectTypes::Enable,
..Dialect::Standard
};
// We could equally have done `dialect = Dialect::Extended`.
let ast = AstModule::parse("json.star", content.to_owned(), &dialect).unwrap();
let globals = Globals::standard();
let module = Module::new();
let mut eval = Evaluator::new(&module);
let res = eval.eval_module(ast, &globals);
// We expect this to fail, since it is a type violation
assert!(res
.unwrap_err()
.to_string()
.contains("Value `test` of type `string` does not match the type annotation `int`"));
}
#[cfg(test)]
#[test]
fn test_starlark_5() {
use starlark::{
environment::{FrozenModule, Globals, Module},
eval::{Evaluator, ReturnFileLoader},
syntax::{AstModule, Dialect},
};
// Get the file contents (for the demo), in reality use `AstModule::parse_file`.
fn get_source(file: &str) -> &str {
match file {
"a.star" => "a = 7",
"b.star" => "b = 6",
_ => {
r#"
load('a.star', 'a')
load('b.star', 'b')
ab = a * b
"#
}
}
}
fn get_module(file: &str) -> starlark::Result<FrozenModule> {
let ast = AstModule::parse(file, get_source(file).to_owned(), &Dialect::Standard)?;
// We can get the loaded modules from `ast.loads`.
// And ultimately produce a `loader` capable of giving those modules to
// Starlark.
let mut loads = Vec::new();
for load in ast.loads() {
loads.push((load.module_id.to_owned(), get_module(load.module_id)?));
}
let modules = loads.iter().map(|(a, b)| (a.as_str(), b)).collect();
let mut loader = ReturnFileLoader { modules: &modules };
let globals = Globals::standard();
let module = Module::new();
{
let mut eval = Evaluator::new(&module);
eval.set_loader(&mut loader);
eval.eval_module(ast, &globals)?;
}
// After creating a module we freeze it, preventing further mutation.
// It can now be used as the input for other Starlark modules.
Ok(module.freeze()?)
}
let ab = get_module("ab.star").unwrap();
assert_eq!(ab.get("ab").unwrap().unpack_i32(), Some(42));
}
#[cfg(test)]
#[test]
fn test_starlark_6() {
use starlark::{
environment::{Globals, Module},
eval::Evaluator,
syntax::{AstModule, Dialect},
};
let content = r#"
def quadratic(a, b, c, x):
return a*x*x + b*x + c
quadratic
"#;
let ast = AstModule::parse("quadratic.star", content.to_owned(), &Dialect::Extended).unwrap();
let globals = Globals::standard();
let module = Module::new();
let mut eval = Evaluator::new(&module);
let quad = eval.eval_module(ast, &globals).unwrap();
let heap = module.heap();
let res = eval
.eval_function(
quad,
&[heap.alloc(4), heap.alloc(2), heap.alloc(1)],
&[("x", heap.alloc(8))],
)
.unwrap();
assert_eq!(res.unpack_i32(), Some(273));
}
#[cfg(test)]
#[test]
fn test_starlark_datetime_fmt() {
use starlark::{
environment::{GlobalsBuilder, Module},
eval::Evaluator,
starlark_module,
syntax::{AstModule, Dialect},
values::Value,
};
let content = r#"
def datetime_fmt():
now_val = now()
print("now returned ", now_val)
n = since(now_val)
print("since returned ", n)
if n < 60 * 60:
return "{} minute{} ago".format((n / (60)), ("" if n / 60 == 1 else "s"))
elif n < 24 * 60 * 60:
return "{} hour{} ago".format( n / (60 * 60), "" if n / (60 * 60) == 1 else "s")
elif n < 7 * 24 * 60 * 60:
return "{} day{} ago".format( n / (24 * 60 * 60), "" if n / (24 * 60 * 60) == 1 else "s")
else:
return "{}".format(n)
datetime_fmt
"#;
// This defines the function that is visible to Starlark
#[starlark_module]
fn starlark_datetime_fmt(builder: &mut GlobalsBuilder) {
fn print(s: Value, val: u64) -> starlark::Result<i32> {
let ss: &str = s.unpack_str().unwrap_or("");
println!("{ss}{val}");
Ok(1)
}
fn now() -> starlark::Result<u64> {
let d = std::time::UNIX_EPOCH;
let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap();
Ok(now.as_secs())
}
fn since(epoch: u64) -> starlark::Result<u64> {
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(epoch);
let now: std::time::Duration = std::time::SystemTime::now()
.duration_since(d)
.unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
Ok(now.as_secs())
}
}
let ast =
AstModule::parse("datetime_fmt.star", content.to_owned(), &Dialect::Extended).unwrap();
let globals = GlobalsBuilder::new().with(starlark_datetime_fmt).build();
let module = Module::new();
let mut eval = Evaluator::new(&module);
let quad = eval.eval_module(ast, &globals).unwrap();
let res = eval.eval_function(quad, &[], &[]).unwrap();
assert_eq!(
res.unpack_str(),
Some("0.0 minutes ago".to_string()).as_deref()
);
}
#[cfg(test)]
#[test]
fn test_starlark_7() {
/*
use starlark::starlark_module;
use std::fmt::Display;
use std::fmt::Write;
use std::fmt::{self};
use allocative::Allocative;
use starlark::environment::Globals;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::starlark_simple_value;
use starlark::syntax::AstModule;
use starlark::syntax::Dialect;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::ProvidesStaticType;
use starlark::values::StarlarkValue;
use starlark::values::Value;
use starlark::values::ValueError;
use starlark::values::ValueLike;
use starlark_derive::starlark_value;
// Define complex numbers
#[derive(Debug, PartialEq, Eq, ProvidesStaticType, NoSerialize, Allocative)]
struct Complex {
real: i32,
imaginary: i32,
}
starlark_simple_value!(Complex);
impl Display for Complex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} + {}i", self.real, self.imaginary)
}
}
#[starlark_value(type = "complex")]
impl<'v> StarlarkValue<'v> for Complex {
// How we add them
fn add(&self, rhs: Value<'v>, heap: &'v Heap) -> Option<starlark::Result<Value<'v>>> {
if let Some(rhs) = rhs.downcast_ref::<Self>() {
Some(Ok(heap.alloc(Complex {
real: self.real + rhs.real,
imaginary: self.imaginary + rhs.imaginary,
})))
} else {
None
}
}
}
let content = "str(a + b)";
let ast = AstModule::parse("complex.star", content.to_owned(), &Dialect::Standard).unwrap();
let globals = Globals::standard();
let module = Module::new();
// We inject some complex numbers into the module before we start.
let a = module.heap().alloc(Complex {
real: 1,
imaginary: 8,
});
module.set("a", a);
let b = module.heap().alloc(Complex {
real: 4,
imaginary: 2,
});
module.set("b", b);
let mut eval = Evaluator::new(&module);
let res = eval.eval_module(ast, &globals).unwrap();
assert_eq!(res.unpack_str(), Some("5 + 10i"));
*/
}