docs.rs failed to build blueprint-starlark-0.13.1
Please check the
build logs for more information.
See
Builds for ideas on how to fix a failed build,
or
Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault,
open an issue.
A Starlark interpreter in Rust.
Starlark is a deterministic version of Python, with a specification,
used by (amongst others) the Buck and Bazel build systems.
To evaluate a simple file:
# fn run() -> starlark::Result<()> {
use blueprint_starlark::environment::Globals;
use blueprint_starlark::environment::Module;
use blueprint_starlark::eval::Evaluator;
use blueprint_starlark::syntax::AstModule;
use blueprint_starlark::syntax::Dialect;
use blueprint_starlark::values::Value;
let content = r#"
def hello():
return "hello"
hello() + " world!"
"#;
let ast: AstModule =
AstModule::parse("hello_world.star", content.to_owned(), &Dialect::Standard)?;
let globals: Globals = Globals::standard();
let module: Module = Module::new();
let mut eval: Evaluator = Evaluator::new(&module);
let res: Value = eval.eval_module(ast, &globals)?;
assert_eq!(res.unpack_str(), Some("hello world!"));
# Ok(())
# }
# fn main(){ run().unwrap(); }
From this example there are lots of ways to extend it, which we do so below.
Call Rust functions from Starlark
We want to define a function in Rust (that computes quadratics), and then call it from Starlark.
We define the function using the #[starlark_module] attribute, and add it to
a Globals object.
#[macro_use]
extern crate starlark;
# fn run() -> starlark::Result<()> {
use blueprint_starlark::environment::{GlobalsBuilder, Module};
use blueprint_starlark::eval::Evaluator;
use blueprint_starlark::syntax::{AstModule, Dialect};
use blueprint_starlark::values::Value;
#[starlark_module]
fn starlark_quadratic(builder: &mut GlobalsBuilder) {
fn quadratic(a: i32, b: i32, c: i32, x: i32) -> anyhow::Result<i32> {
Ok(a * x * x + b * x + c)
}
}
let globals = GlobalsBuilder::new().with(starlark_quadratic).build();
let module = Module::new();
let mut eval = Evaluator::new(&module);
let starlark_code = r#"
quadratic(4, 2, 1, x = 8)
"#;
let ast = AstModule::parse("quadratic.star", starlark_code.to_owned(), &Dialect::Standard)?;
let res = eval.eval_module(ast, &globals)?;
assert_eq!(res.unpack_i32(), Some(273)); # Ok(())
# }
# fn main(){ run().unwrap(); }
Collect Starlark values
If we want to use Starlark as an enhanced JSON, we can define an emit function
to "write out" a JSON value, and use the Evaluator.extra field to store it.
#[macro_use]
extern crate starlark;
# fn run() -> starlark::Result<()> {
use std::cell::RefCell;
use blueprint_starlark::any::ProvidesStaticType;
use blueprint_starlark::environment::GlobalsBuilder;
use blueprint_starlark::environment::Module;
use blueprint_starlark::eval::Evaluator;
use blueprint_starlark::syntax::AstModule;
use blueprint_starlark::syntax::Dialect;
use blueprint_starlark::values::Value;
use blueprint_starlark::values::ValueLike;
use blueprint_starlark::values::none::NoneType;
let content = r#"
emit(1)
emit(["test"])
emit({"x": "y"})
"#;
#[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) -> anyhow::Result<NoneType> {
eval.extra
.unwrap()
.downcast_ref::<Store>()
.unwrap()
.add(x.to_json()?);
Ok(NoneType)
}
}
let ast = AstModule::parse("json.star", content.to_owned(), &Dialect::Standard)?;
let globals = GlobalsBuilder::new().with(starlark_emit).build();
let module = Module::new();
let store = Store::default();
{
let mut eval = Evaluator::new(&module);
eval.extra = Some(&store);
eval.eval_module(ast, &globals)?;
}
assert_eq!(&*store.0.borrow(), &["1", "[\"test\"]", "{\"x\":\"y\"}"]);
# Ok(())
# }
# fn main(){ run().unwrap(); }
Enable Starlark extensions (e.g. types)
Our Starlark supports a number of extensions, including type annotations, which are
controlled by the Dialect type.
# fn run() -> starlark::Result<()> {
use blueprint_starlark::environment::Globals;
use blueprint_starlark::environment::Module;
use blueprint_starlark::eval::Evaluator;
use blueprint_starlark::syntax::AstModule;
use blueprint_starlark::syntax::Dialect;
use blueprint_starlark::syntax::DialectTypes;
let content = r#"
def takes_int(x: int):
pass
takes_int("test")
"#;
let dialect = Dialect {
enable_types: DialectTypes::Enable,
..Dialect::Standard
};
let ast = AstModule::parse("json.star", content.to_owned(), &dialect)?;
let globals = Globals::standard();
let module = Module::new();
let mut eval = Evaluator::new(&module);
let res = eval.eval_module(ast, &globals);
assert!(
res.unwrap_err()
.to_string()
.contains("Value `test` of type `string` does not match the type annotation `int`")
);
# Ok(())
# }
# fn main(){ run().unwrap(); }
Enable the load statement
You can have Starlark load files imported by the user.
That requires that the loaded modules are first frozen with Module.freeze.
There is no requirement that the files are on disk, but that would be a common pattern.
# fn run() -> starlark::Result<()> {
use blueprint_starlark::environment::FrozenModule;
use blueprint_starlark::environment::Globals;
use blueprint_starlark::environment::Module;
use blueprint_starlark::eval::Evaluator;
use blueprint_starlark::eval::ReturnFileLoader;
use blueprint_starlark::syntax::AstModule;
use blueprint_starlark::syntax::Dialect;
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)?;
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)?;
}
Ok(module.freeze()?)
}
let ab = get_module("ab.star")?;
assert_eq!(ab.get("ab").unwrap().unpack_i32(), Some(42));
# Ok(())
# }
# fn main(){ run().unwrap(); }
Call a Starlark function from Rust
You can extract functions from Starlark, and call them from Rust, using eval_function.
# fn run() -> starlark::Result<()> {
use blueprint_starlark::environment::Globals;
use blueprint_starlark::environment::Module;
use blueprint_starlark::eval::Evaluator;
use blueprint_starlark::syntax::AstModule;
use blueprint_starlark::syntax::Dialect;
use blueprint_starlark::values::Value;
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::Standard)?;
let globals = Globals::standard();
let module = Module::new();
let mut eval = Evaluator::new(&module);
let quad = eval.eval_module(ast, &globals)?;
let heap = module.heap();
let res = eval.eval_function(
quad,
&[heap.alloc(4), heap.alloc(2), heap.alloc(1)],
&[("x", heap.alloc(8))],
)?;
assert_eq!(res.unpack_i32(), Some(273));
# Ok(())
# }
# fn main(){ run().unwrap(); }
Defining Rust objects that are used from Starlark
Finally, we can define our own types in Rust which live in the Starlark heap.
Such types are relatively complex, see the details at StarlarkValue.
# fn run() -> starlark::Result<()> {
use std::fmt::Display;
use std::fmt::Write;
use std::fmt::{self};
use blueprint_allocative::Allocative;
use blueprint_starlark::environment::Globals;
use blueprint_starlark::environment::Module;
use blueprint_starlark::eval::Evaluator;
use blueprint_starlark::starlark_simple_value;
use blueprint_starlark::syntax::AstModule;
use blueprint_starlark::syntax::Dialect;
use blueprint_starlark::values::Heap;
use blueprint_starlark::values::NoSerialize;
use blueprint_starlark::values::ProvidesStaticType;
use blueprint_starlark::values::StarlarkValue;
use blueprint_starlark::values::Value;
use blueprint_starlark::values::ValueError;
use blueprint_starlark::values::ValueLike;
use blueprint_starlark_derive::starlark_value;
#[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 {
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)?;
let globals = Globals::standard();
let module = Module::new();
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)?;
assert_eq!(res.unpack_str(), Some("5 + 10i"));
# Ok(())
# }
# fn main(){ run().unwrap(); }