diff --git a/examples/simple.rs b/examples/simple.rs
index 094d503..8c5e2fa 100644
--- a/examples/simple.rs
+++ b/examples/simple.rs
@@ -18,4 +18,4 @@ fn main() {
// Debug formatting
let result = formatx!("{:?}", "a string").unwrap();
println!("{result}");
-}
+}
\ No newline at end of file
diff --git a/examples/struct.rs b/examples/struct.rs
index 927023e..07cb953 100644
--- a/examples/struct.rs
+++ b/examples/struct.rs
@@ -30,12 +30,14 @@ fn main() {
// Template reuse
let template = formatx::Template::new("Point {name}: {point}").unwrap();
- let r1 = template.render()
+ let r1 = template
+ .render()
.named("name", &"Origin")
.named("point", &origin)
.finish()
.unwrap();
- let r2 = template.render()
+ let r2 = template
+ .render()
.named("name", &"Target")
.named("point", &target)
.finish()
diff --git a/images/benchmark.svg b/images/benchmark.svg
index c3d5678..d3bc5a1 100644
--- a/images/benchmark.svg
+++ b/images/benchmark.svg
@@ -8,32 +8,32 @@
0
-87
+56
-174
+111
-261
+167
-348
-
-27 ns
-
-250 ns
+223
+
+19 ns
+
+194 ns
{} + {} = {}
-
-28 ns
-
-229 ns
+
+16 ns
+
+164 ns
{name} scored {score}%
-
-97 ns
-
-223 ns
+
+80 ns
+
+172 ns
{:+08.2}
-
-149 ns
-
-303 ns
+
+104 ns
+
+183 ns
{:-^20}
Expected overhead - formatx! parses and resolves format strings at runtime.
diff --git a/src/ast.rs b/src/ast.rs
index 5dc7452..e69b22c 100644
--- a/src/ast.rs
+++ b/src/ast.rs
@@ -1,5 +1,7 @@
//! Typed AST for parsed format strings.
+use alloc::vec::Vec;
+
/// Byte range in the source format string.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
@@ -51,7 +53,7 @@ pub enum Argument {
}
/// The full format specification after `:` inside a placeholder.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
pub struct FormatSpec {
pub fill: Option,
pub align: Option,
@@ -82,13 +84,7 @@ impl FormatSpec {
/// meaning we can take the fast path and `write!` directly.
#[inline]
pub fn is_default(&self) -> bool {
- self.fill.is_none()
- && self.align.is_none()
- && self.sign.is_none()
- && !self.alternate
- && !self.zero_pad
- && self.width.is_none()
- && self.precision.is_none()
+ self == &Self::default()
}
}
@@ -108,7 +104,7 @@ pub enum Sign {
}
/// A width or precision value - either a literal number or a parameter reference.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
pub enum Count {
/// A literal integer, e.g. `10` in `{:10}`.
Literal(usize),
@@ -117,7 +113,7 @@ pub enum Count {
}
/// A parameter reference for width/precision.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
pub enum CountParam {
/// `{:0$}` - positional argument index.
Positional(usize),
@@ -126,7 +122,7 @@ pub enum CountParam {
}
/// Precision specification.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
pub enum Precision {
/// `.5` or `.prec$` - a count value.
Count(Count),
diff --git a/src/error.rs b/src/error.rs
index 199b31b..0bb97cf 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,26 +1,18 @@
//! Error types for formatx.
use crate::ast::{FormatType, Span};
-use std::fmt;
+use alloc::string::String;
+use core::fmt;
/// Errors that can occur during parsing or formatting.
#[derive(Debug)]
pub enum Error {
/// The format string could not be parsed.
- Parse {
- span: Span,
- message: String,
- },
+ Parse { span: Span, message: String },
/// A placeholder references an argument that was not provided.
- MissingArgument {
- name: String,
- span: Span,
- },
+ MissingArgument { name: String, span: Span },
/// A format type (e.g. `{:x}`) requires a trait we don't support.
- UnsupportedTrait {
- format_type: FormatType,
- span: Span,
- },
+ UnsupportedTrait { format_type: FormatType, span: Span },
/// An underlying `std::fmt::Error` occurred during formatting.
Format(fmt::Error),
}
@@ -52,8 +44,8 @@ impl fmt::Display for Error {
}
}
-impl std::error::Error for Error {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+impl core::error::Error for Error {
+ fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
if let Self::Format(e) = self {
Some(e)
} else {
diff --git a/src/format.rs b/src/format.rs
index eae70b1..b213fd4 100644
--- a/src/format.rs
+++ b/src/format.rs
@@ -1,15 +1,25 @@
//! Format engine - applies [`FormatSpec`] to produce formatted output.
-use crate::{ast::*, error::Error, value::FormatValue};
-use std::fmt::{Debug, Write};
+use crate::{
+ ast::*,
+ error::Error,
+ value::{FormatArg, FormatValue},
+};
+use alloc::{
+ format,
+ string::{String, ToString},
+};
+use core::fmt::{Debug, Write};
+
+type NamedArgs<'a> = [(&'a str, usize)];
/// Render a parsed [`FormatString`] into `output` using the provided arguments.
pub fn render(
output: &mut String,
source: &str,
parsed: &FormatString,
- args: &[&dyn FormatValue],
- named: &[(&str, usize)],
+ args: &[FormatArg],
+ named: &NamedArgs,
strict: bool,
) -> Result<(), Error> {
let mut implicit_pos: usize = 0;
@@ -36,7 +46,7 @@ pub fn render(
resolve_argument(&placeholder.argument, source, &mut implicit_pos, named);
let arg = match arg_index {
- Some(idx) if idx < args.len() => Some(args[idx]),
+ Some(idx) if idx < args.len() => Some(args[idx].as_value()),
Some(_) | None => None,
};
@@ -102,7 +112,7 @@ fn resolve_argument(
argument: &Argument,
source: &str,
implicit_pos: &mut usize,
- named: &[(&str, usize)],
+ named: &NamedArgs,
) -> Option {
match argument {
Argument::Implicit => {
@@ -121,8 +131,8 @@ fn resolve_argument(
fn resolve_count_value(
count: &Option,
source: &str,
- args: &[&dyn FormatValue],
- named: &[(&str, usize)],
+ args: &[FormatArg],
+ named: &NamedArgs,
) -> Result