Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for captured arguments from macros #688

Merged
merged 1 commit into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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