Skip to content

Commit

Permalink
Add support for captured arguments from macros
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Apr 14, 2024
1 parent 1e05a36 commit 86c0fc8
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 87 deletions.
2 changes: 1 addition & 1 deletion book/src/hot_reloading.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A typical way to accomplish this is to watch a scripts directory using the
to the directory are detected. See the [`hot_reloading` example] and in
particular the [`PathReloader`] type.

```
```rust
{{#include ../../examples/examples/hot_reloading.rs}}
```

Expand Down
15 changes: 8 additions & 7 deletions book/src/multithreading.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ used by multiple threads simultaneously through `Arc<Unit>` and
`Arc<RuntimeContext>`. Constructing a `Vm` with these through `Vm::new` is a
very cheap operation.

> `Vm`'s do allocate a stack, to avoid this you'd have to employ even more
> advanced techniques, such as storing the virtual machine in a thread local and
> using it by swapping out the `Unit` and `RuntimeContext` associated with it
> through [`Vm::unit_mut`] and [`Vm::context_mut`] respectively.
```rust
let unit: Arc<Unit> = /* todo */;
let context: Arc<RuntimeContext> = /* todo */;
Expand All @@ -30,18 +25,24 @@ std::thread::spawn(move || {
});
```

> Virtual machines do allocate memory. To this overhead too you'd have to employ
> more advanced techniques, such as storing virtual machines in a pool or thread
> locals. Once a machine has been acquired the `Unit` and `RuntimeContext`
> associated with it can be swapped out to the ones you need using
> [`Vm::unit_mut`] and [`Vm::context_mut`] respectively.
Using [`Vm::send_execute`] is a way to assert that a given execution is thread
safe. And allows you to use Rune in asynchronous multithreaded environments,
such as Tokio. This is achieved by ensuring that all captured arguments are
[`ConstValue`]'s, which in contrast to [`Value`]'s are guaranteed to be
thread-safe:

```
```rust
{{#include ../../examples/examples/tokio_spawn.rs}}
```

Finally [`Function::into_sync`] exists to coerce a function into a
[`SyncFunction], which is a thread-safe variant of a regular [`Function`]. This
[`SyncFunction`], which is a thread-safe variant of a regular [`Function`]. This
is a fallible operation since all values which are captured in the function-type
in case its a closure has to be coerced to [`ConstValue`]. If this is not the
case, the conversion will fail.
Expand Down
1 change: 0 additions & 1 deletion crates/rune/src/compile/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ impl Context {
this.install(crate::modules::fmt::module()?)?;
this.install(crate::modules::future::module()?)?;
this.install(crate::modules::i64::module()?)?;
#[cfg(feature = "std")]
this.install(crate::modules::io::module(stdio)?)?;
this.install(crate::modules::iter::module()?)?;
this.install(crate::modules::macros::module()?)?;
Expand Down
17 changes: 9 additions & 8 deletions crates/rune/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,17 @@
//! * Runs a compact representation of the language on top of an efficient
//! [stack-based virtual machine][support-virtual-machine].
//! * Clean [Rust integration 💻][support-rust-integration].
//! * [Multithreaded 📖][support-multithreading] execution.
//! * [Hot reloading 📖][support-hot-reloading].
//! * Memory safe through [reference counting 📖][support-reference-counted].
//! * [Awesome macros 📖][support-macros].
//! * [Template literals 📖][support-templates].
//! * [Try operators 📖][support-try].
//! * [Pattern matching 📖][support-patterns].
//! * [Awesome macros 📖][support-macros] and [Template literals 📖][support-templates].
//! * [Try operators 📖][support-try] and [Pattern matching 📖][support-patterns].
//! * [Structs and enums 📖][support-structs] with associated data and
//! functions.
//! * Dynamic [vectors 📖][support-dynamic-vectors], [objects
//! 📖][support-anon-objects], and [tuples 📖][support-anon-tuples] with
//! * Dynamic containers like [vectors 📖][support-dynamic-vectors], [objects
//! 📖][support-anon-objects], and [tuples 📖][support-anon-tuples] all with
//! out-of-the-box [serde support 💻][support-serde].
//! * First-class [async support 📖][support-async].
//! * [Generators 📖][support-generators].
//! * First-class [async support 📖][support-async] with [Generators 📖][support-generators].
//! * Dynamic [instance functions 📖][support-instance-functions].
//! * [Stack isolation 📖][support-stack-isolation] between function calls.
//!
Expand Down Expand Up @@ -114,8 +113,10 @@
//! [support-async]: https://rune-rs.github.io/book/async.html
//! [support-dynamic-vectors]: https://rune-rs.github.io/book/vectors.html
//! [support-generators]: https://rune-rs.github.io/book/generators.html
//! [support-hot-reloading]: https://rune-rs.github.io/book/hot_reloading.html
//! [support-instance-functions]: https://rune-rs.github.io/book/instance_functions.html
//! [support-macros]: https://rune-rs.github.io/book/macros.html
//! [support-multithreading]: https://rune-rs.github.io/book/multithreading.html
//! [support-patterns]: https://rune-rs.github.io/book/pattern_matching.html
//! [support-reference-counted]: https://rune-rs.github.io/book/variables.html
//! [support-rust-integration]: https://github.com/rune-rs/rune/tree/main/crates/rune-modules
Expand Down
154 changes: 89 additions & 65 deletions crates/rune/src/macros/format_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::alloc::prelude::*;
use crate::alloc::{self, BTreeMap, BTreeSet, Box, HashMap, String, Vec};
use crate::ast::{self, Span, Spanned};
use crate::compile::{self, WithSpan};
use crate::macros::{quote, MacroContext, Quote};
use crate::macros::{quote, MacroContext, Quote, ToTokens, TokenStream};
use crate::parse::{Parse, Parser, Peek, Peeker};
use crate::runtime::format;
use crate::runtime::ValueKind;
Expand Down Expand Up @@ -49,14 +49,11 @@ impl FormatArgs {
}
}

let format = match format.take_kind().with_span(&self.format)? {
ValueKind::String(string) => string,
_ => {
return Err(compile::Error::msg(
&self.format,
"format argument must be a string",
));
}
let ValueKind::String(format) = format.take_kind().with_span(&self.format)? else {
return Err(compile::Error::msg(
&self.format,
"format argument must be a string",
));
};

let mut unused_pos = (0..pos.len()).try_collect::<BTreeSet<_>>()?;
Expand All @@ -65,19 +62,19 @@ impl FormatArgs {
.map(|(key, n)| Ok::<_, alloc::Error>((key.try_clone()?, n.span())))
.try_collect::<alloc::Result<BTreeMap<_, _>>>()??;

let expanded = match expand_format_spec(
let result = expand_format_spec(
cx,
self.format.span(),
&format,
&pos,
&mut unused_pos,
&named,
&mut unused_named,
) {
);

let expanded = match result {
Ok(expanded) => expanded,
Err(message) => {
return Err(compile::Error::msg(self.format.span(), message));
}
Err(message) => return Err(compile::Error::msg(self.format.span(), message)),
};

if let Some(expr) = unused_pos.into_iter().flat_map(|n| pos.get(n)).next() {
Expand Down Expand Up @@ -174,18 +171,27 @@ fn expand_format_spec<'a>(
let mut name = String::new();
let mut width = String::new();
let mut precision = String::new();
let mut buf = String::new();

let mut buf = String::new();
let mut components = Vec::new();
let mut count = 0;
let mut start = Some(0);

while let Some(value) = iter.next() {
match value {
while let Some((at, a, b)) = iter.next() {
match (a, b) {
('}', '}') => {
if let Some(start) = start.take() {
buf.try_push_str(&input[start..at])?;
}

buf.try_push('}')?;
iter.next();
}
('{', '{') => {
if let Some(start) = start.take() {
buf.try_push_str(&input[start..at])?;
}

buf.try_push('{')?;
iter.next();
}
Expand All @@ -196,8 +202,12 @@ fn expand_format_spec<'a>(
));
}
('{', _) => {
if let Some(start) = start.take() {
buf.try_push_str(&input[start..at])?;
}

if !buf.is_empty() {
components.try_push(C::Literal(buf.try_clone()?.try_into_boxed_str()?))?;
components.try_push(C::Literal(Box::try_from(&buf[..])?))?;
buf.clear();
}

Expand All @@ -215,14 +225,20 @@ fn expand_format_spec<'a>(
unused_named,
)?)?;
}
(a, _) => {
buf.try_push(a)?;
_ => {
if start.is_none() {
start = Some(at);
}
}
}
}

if let Some(start) = start.take() {
buf.try_push_str(&input[start..])?;
}

if !buf.is_empty() {
components.try_push(C::Literal(buf.try_clone()?.try_into_boxed_str()?))?;
components.try_push(C::Literal(Box::try_from(&buf[..])?))?;
buf.clear();
}

Expand All @@ -235,7 +251,7 @@ fn expand_format_spec<'a>(
for c in components {
match c {
C::Literal(literal) => {
let lit = cx.lit(&*literal)?;
let lit = cx.lit(literal.as_ref())?;
args.try_push(quote!(#lit))?;
}
C::Format {
Expand Down Expand Up @@ -314,10 +330,28 @@ fn expand_format_spec<'a>(
#[builtin] template!(#(args),*)
});

enum ExprOrIdent<'a> {
Expr(&'a ast::Expr),
Ident(ast::Ident),
}

impl ToTokens for ExprOrIdent<'_> {
fn to_tokens(
&self,
cx: &mut MacroContext<'_, '_, '_>,
stream: &mut TokenStream,
) -> alloc::Result<()> {
match self {
Self::Expr(expr) => expr.to_tokens(cx, stream),
Self::Ident(ident) => ident.to_tokens(cx, stream),
}
}
}

enum C<'a> {
Literal(Box<str>),
Format {
expr: &'a ast::Expr,
expr: ExprOrIdent<'a>,
fill: Option<char>,
align: Option<format::Alignment>,
width: Option<usize>,
Expand Down Expand Up @@ -384,11 +418,8 @@ fn expand_format_spec<'a>(
let mut mode = Mode::Start;

loop {
let (a, b) = match iter.current() {
Some(item) => item,
_ => {
return Err(compile::Error::msg(span, "unexpected end of format string"));
}
let Some((_, a, b)) = iter.current() else {
return Err(compile::Error::msg(span, "unexpected end of format string"));
};

match mode {
Expand Down Expand Up @@ -577,7 +608,20 @@ fn expand_format_spec<'a>(
None
};

let expr = if !name.is_empty() {
let expr = 'expr: {
if name.is_empty() {
let Some(expr) = pos.get(*count) else {
return Err(compile::Error::msg(
span,
format!("missing positional argument #{count}"),
));
};

unused_pos.remove(count);
*count += 1;
break 'expr ExprOrIdent::Expr(expr);
};

if let Ok(n) = str::parse::<usize>(name) {
let expr = match pos.get(n) {
Some(expr) => *expr,
Expand All @@ -590,35 +634,17 @@ fn expand_format_spec<'a>(
};

unused_pos.remove(&n);
expr
} else {
let expr = match named.get(name.as_str()) {
Some(n) => &n.expr,
None => {
return Err(compile::Error::msg(
span,
format!("missing named argument `{}`", name),
));
}
};
break 'expr ExprOrIdent::Expr(expr);
}

if let Some(n) = named.get(name.as_str()) {
unused_named.remove(name.as_str());
expr
break 'expr ExprOrIdent::Expr(&n.expr);
}
} else {
let expr = match pos.get(*count) {
Some(expr) => *expr,
None => {
return Err(compile::Error::msg(
span,
format!("missing positional argument #{}", count),
));
}
};

unused_pos.remove(count);
*count += 1;
expr
let mut ident = cx.ident(name.as_str())?;
ident.span = span;
ExprOrIdent::Ident(ident)
};

let width = if !width.is_empty() {
Expand Down Expand Up @@ -648,30 +674,28 @@ fn expand_format_spec<'a>(
}

struct Iter<'a> {
iter: str::Chars<'a>,
a: Option<char>,
b: Option<char>,
iter: str::CharIndices<'a>,
a: Option<(usize, char)>,
b: Option<(usize, char)>,
}

impl<'a> Iter<'a> {
fn new(input: &'a str) -> Self {
let mut iter = input.chars();

let mut iter = input.char_indices();
let a = iter.next();
let b = iter.next();

Self { iter, a, b }
}

fn current(&self) -> Option<(char, char)> {
let a = self.a?;
let b = self.b.unwrap_or_default();
Some((a, b))
fn current(&self) -> Option<(usize, char, char)> {
let (pos, a) = self.a?;
let (_, b) = self.b.unwrap_or_default();
Some((pos, a, b))
}
}

impl Iterator for Iter<'_> {
type Item = (char, char);
type Item = (usize, char, char);

fn next(&mut self) -> Option<Self::Item> {
let value = self.current()?;
Expand Down
2 changes: 1 addition & 1 deletion crates/rune/src/macros/token_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ pub trait ToTokens {
/// Turn the current item into tokens.
fn to_tokens(
&self,
context: &mut MacroContext<'_, '_, '_>,
cx: &mut MacroContext<'_, '_, '_>,
stream: &mut TokenStream,
) -> alloc::Result<()>;
}
Expand Down
1 change: 0 additions & 1 deletion crates/rune/src/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ pub mod future;
pub mod generator;
pub mod hash;
pub mod i64;
#[cfg(feature = "std")]
pub mod io;
pub mod iter;
pub mod macros;
Expand Down
Loading

0 comments on commit 86c0fc8

Please sign in to comment.