1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! A [Starlark interpreter in Rust](https://github.com/facebookexperimental/starlark-rust).
//! Starlark is a deterministic version of Python, with [a specification](https://github.com/bazelbuild/starlark/blob/master/spec.md),
//! used by (amongst others) the [Buck](https://buck.build) and [Bazel](https://bazel.build) build systems.
//!
//! To evaluate a simple file:
//!
//! ```
//! # fn run() -> anyhow::Result<()> {
//! use starlark::eval::Evaluator;
//! use starlark::environment::{Module, Globals};
//! use starlark::values::Value;
//! use starlark::syntax::{AstModule, Dialect};
//!
//! 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)?;
//!
//! // 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, &globals);
//!
//! // And finally we evaluate the code using the evaluator.
//! let res: Value = eval.eval_module(ast)?;
//! 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]`](macro@starlark_module) attribute, and add it to
//! a [`Globals`](environment::Globals) object.
//!
//! ```
//! #[macro_use]
//! extern crate starlark_module;
//! # fn run() -> anyhow::Result<()> {
//! use starlark::environment::{GlobalsBuilder, Module};
//! use starlark::eval::Evaluator;
//! use starlark::syntax::{AstModule, Dialect};
//! use starlark::values::Value;
//!
//! let content = r#"
//! quadratic(4, 2, 1, x = 8)
//! "#;
//!
//! #[starlark_module]
//! fn starlark_quadratic(builder: &mut GlobalsBuilder) {
//!     // This defines a function that is visible to Starlark
//!     fn quadratic(a: i32, b: i32, c: i32, x: i32) -> i32 {
//!         Ok(a * x * x + b * x + c)
//!     }
//! }
//!
//! let ast = AstModule::parse("quadratic.star", content.to_owned(), &Dialect::Standard)?;
//! // We build our globals adding some functions we wrote
//! let globals = GlobalsBuilder::new().with(starlark_quadratic).build();
//! let module = Module::new();
//! let mut eval = Evaluator::new(&module, &globals);
//! let res = eval.eval_module(ast)?;
//! assert_eq!(res.unpack_int(), 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`](eval::Evaluator::extra) field to store it.
//!
//! ```
//! #[macro_use]
//! extern crate starlark_module;
//! # fn run() -> anyhow::Result<()> {
//! use starlark::environment::{GlobalsBuilder, Module};
//! use starlark::eval::Evaluator;
//! use starlark::syntax::{AstModule, Dialect};
//! use starlark::values::{none::NoneType, Value, ValueLike};
//! use gazebo::any::AnyLifetime;
//! use std::cell::RefCell;
//!
//! let content = r#"
//! emit(1)
//! emit(["test"])
//! emit({"x": "y"})
//! "#;
//!
//! // Define a store in which to accumulate JSON strings
//! #[derive(Debug, AnyLifetime, 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) -> NoneType {
//!         // We modify extra (which we know is a Store) and add the JSON of the
//!         // value the user gave.
//!         ctx.extra
//!             .unwrap()
//!             .downcast_ref::<Store>()
//!             .unwrap()
//!             .add(x.to_json());
//!         Ok(NoneType)
//!     }
//! }
//!
//! let ast = AstModule::parse("json.star", content.to_owned(), &Dialect::Standard)?;
//! // We build our globals adding some functions we wrote
//! let globals = GlobalsBuilder::new().with(starlark_emit).build();
//! let module = Module::new();
//! let mut eval = Evaluator::new(&module, &globals);
//! // We add a reference to our store
//! let store = Store::default();
//! eval.extra = Some(&store);
//! eval.eval_module(ast)?;
//! 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`](syntax::Dialect) type.
//!
//! ```
//! # fn run() -> anyhow::Result<()> {
//! use starlark::environment::{Globals, Module};
//! use starlark::eval::Evaluator;
//! use starlark::syntax::{AstModule, Dialect};
//!
//! let content = r#"
//! def takes_int(x: int.type):
//!     pass
//! takes_int("test")
//! "#;
//!
//! // Make the dialect enable types
//! let dialect = Dialect {enable_types: true, ..Dialect::Standard};
//! // We could equally have done `dialect = Dialect::Extended`.
//! let ast = AstModule::parse("json.star", content.to_owned(), &dialect)?;
//! let globals = Globals::standard();
//! let module = Module::new();
//! let mut eval = Evaluator::new(&module, &globals);
//! let res = eval.eval_module(ast);
//! // 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`"));
//! # 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`](environment::Module::freeze).
//! There is no requirement that the files are on disk, but that would be a common pattern.
//!
//! ```
//! # fn run() -> anyhow::Result<()> {
//! use starlark::environment::{FrozenModule, Globals, Module};
//! use starlark::eval::{Evaluator, ReturnFileLoader};
//! use starlark::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) -> anyhow::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.to_owned(), get_module(load)?));
//!    }
//!    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, &globals);
//!    eval.set_loader(&mut loader);
//!    eval.eval_module(ast)?;
//!    // 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")?;
//! assert_eq!(ab.get("ab").unwrap().unpack_int(), 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`](eval::Evaluator::eval_function).
//!
//! ```
//! # fn run() -> anyhow::Result<()> {
//! use starlark::environment::{Globals, Module};
//! use starlark::eval::Evaluator;
//! use starlark::syntax::{AstModule, Dialect};
//! use 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::Extended)?;
//! let globals = Globals::standard();
//! let module = Module::new();
//! let mut eval = Evaluator::new(&module, &globals);
//! let quad = eval.eval_module(ast)?;
//! let res = eval.eval_function(
//!     quad,
//!     &[Value::new_int(4), Value::new_int(2), Value::new_int(1)],
//!     &[("x", Value::new_int(8))],
//! )?;
//! assert_eq!(res.unpack_int(), 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`](values::StarlarkValue).
//!
//! ```
//! # fn run() -> anyhow::Result<()> {
//! use starlark::environment::{Globals, Module};
//! use starlark::eval::Evaluator;
//! use starlark::syntax::{AstModule, Dialect};
//! use starlark::values::{Heap, SimpleValue, StarlarkValue, Value, ValueError};
//! use starlark::{starlark_type, starlark_simple_value};
//!
//! // Define complex numbers
//! #[derive(Debug, PartialEq, Eq)]
//! struct Complex {
//!     real: i32,
//!     imaginary: i32,
//! }
//! starlark_simple_value!(Complex);
//!
//! impl<'v> StarlarkValue<'v> for Complex {
//!     starlark_type!("complex");
//!
//!     // How we display them
//!     fn collect_repr(&self, collector: &mut String) {
//!         collector.push_str(&format!("{} + {}i", self.real, self.imaginary))
//!     }
//!
//!     // How we add them
//!     fn add(&self, rhs: Value<'v>, heap: &'v Heap)
//!             -> anyhow::Result<Value<'v>> {
//!         if let Some(rhs) = rhs.downcast_ref::<Self>() {
//!             Ok(heap.alloc(Complex {
//!                 real: self.real + rhs.real,
//!                 imaginary: self.imaginary + rhs.imaginary,
//!             }))
//!         } else {
//!             ValueError::unsupported_with(self, "+", rhs)
//!         }
//!     }
//! }
//!
//! let content = "str(a + b)";
//!
//! let ast = AstModule::parse("complex.star", content.to_owned(), &Dialect::Standard)?;
//! 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, &globals);
//! let res = eval.eval_module(ast)?;
//! assert_eq!(res.unpack_str(), Some("5 + 10i"));
//! # Ok(())
//! # }
//! # fn main(){ run().unwrap(); }
//! ```

// Features we use
#![feature(backtrace)]
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(hash_set_entry)]
#![feature(try_blocks)]
//
// Plugins
#![cfg_attr(feature = "custom_linter", feature(plugin))]
#![cfg_attr(feature = "custom_linter", allow(deprecated))] // :(
#![cfg_attr(feature = "custom_linter", plugin(linter))]
//
// Good reasons
#![allow(clippy::new_ret_no_self)] // We often return Value, even though its morally a Self
#![allow(clippy::needless_return)] // Mixing explicit returns with implicit ones sometimes looks odd
// Disagree these are good hints
#![allow(clippy::single_match)]
#![allow(clippy::should_implement_trait)]
#![allow(clippy::len_without_is_empty)]
#![allow(clippy::match_like_matches_macro)]
#![allow(clippy::new_without_default)]
#![allow(clippy::type_complexity)]
#![allow(clippy::needless_lifetimes)]
// FIXME: Temporary
#![allow(clippy::useless_transmute)] // Seems to be a clippy bug, but we should be using less transmute anyway

#[macro_use]
extern crate gazebo;
#[macro_use]
extern crate starlark_module;
#[macro_use]
extern crate maplit;
#[macro_use]
mod macros;

// There's only one thing in analysis, and that's LineCol1
// which is itself hidden (and slated for removal) so hide that analysis exists
#[doc(hidden)]
pub mod analysis;

pub mod assert;
pub mod codemap;
pub mod collections;
mod debug;
pub mod environment;
pub mod errors;
pub mod eval;
mod stdlib;
pub mod syntax;
pub mod values;

/// __macro_refs allows us to reference other crates in macro rules without users needing to be
///  aware of those dependencies. We make them public here and then can reference them like
///  `$crate::__macro_refs::foo`.
#[doc(hidden)]
pub mod __macro_refs {
    pub use gazebo::any_lifetime;
    pub use paste::item;
}