mirror of
https://github.com/typst/typst
synced 2025-08-17 16:38:31 +08:00
Compare commits
7 Commits
5fd3aa68fe
...
6748a86916
Author | SHA1 | Date | |
---|---|---|---|
|
6748a86916 | ||
|
f51cb4b03e | ||
|
0c12828c9a | ||
|
b1a091a236 | ||
|
490327597b | ||
|
02495899ec | ||
|
0fb01fccc3 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3247,7 +3247,7 @@ dependencies = [
|
|||||||
name = "typst-timing"
|
name = "typst-timing"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.7.1",
|
"ecow",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -46,7 +46,7 @@ impl i64 {
|
|||||||
/// or smaller than the minimum 64-bit signed integer.
|
/// or smaller than the minimum 64-bit signed integer.
|
||||||
///
|
///
|
||||||
/// - Booleans are converted to `0` or `1`.
|
/// - Booleans are converted to `0` or `1`.
|
||||||
/// - Floats and decimals are truncated to the next 64-bit integer.
|
/// - Floats and decimals are rounded to the next 64-bit integer towards zero.
|
||||||
/// - Strings are parsed in base 10.
|
/// - Strings are parsed in base 10.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
|
@ -20,8 +20,8 @@ use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
|
|||||||
/// A mathematical equation.
|
/// A mathematical equation.
|
||||||
///
|
///
|
||||||
/// Can be displayed inline with text or as a separate block. An equation
|
/// Can be displayed inline with text or as a separate block. An equation
|
||||||
/// becomes block-level through the presence of at least one space after the
|
/// becomes block-level through the presence of whitespace after the opening
|
||||||
/// opening dollar sign and one space before the closing dollar sign.
|
/// dollar sign and whitespace before the closing dollar sign.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```example
|
/// ```example
|
||||||
@ -41,9 +41,9 @@ use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
|
|||||||
///
|
///
|
||||||
/// # Syntax
|
/// # Syntax
|
||||||
/// This function also has dedicated syntax: Write mathematical markup within
|
/// This function also has dedicated syntax: Write mathematical markup within
|
||||||
/// dollar signs to create an equation. Starting and ending the equation with at
|
/// dollar signs to create an equation. Starting and ending the equation with
|
||||||
/// least one space lifts it into a separate block that is centered
|
/// whitespace lifts it into a separate block that is centered horizontally.
|
||||||
/// horizontally. For more details about math syntax, see the
|
/// For more details about math syntax, see the
|
||||||
/// [main math page]($category/math).
|
/// [main math page]($category/math).
|
||||||
#[elem(Locatable, Synthesize, ShowSet, Count, LocalName, Refable, Outlinable)]
|
#[elem(Locatable, Synthesize, ShowSet, Count, LocalName, Refable, Outlinable)]
|
||||||
pub struct EquationElem {
|
pub struct EquationElem {
|
||||||
|
@ -92,7 +92,7 @@ pub(super) fn define(global: &mut Scope) {
|
|||||||
/// ```
|
/// ```
|
||||||
#[elem(Debug, Construct, PlainText, Repr)]
|
#[elem(Debug, Construct, PlainText, Repr)]
|
||||||
pub struct TextElem {
|
pub struct TextElem {
|
||||||
/// A font family descriptor or priority list of font family descriptor.
|
/// A font family descriptor or priority list of font family descriptors.
|
||||||
///
|
///
|
||||||
/// A font family descriptor can be a plain string representing the family
|
/// A font family descriptor can be a plain string representing the family
|
||||||
/// name or a dictionary with the following keys:
|
/// name or a dictionary with the following keys:
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
@ -17,12 +19,12 @@ pub struct Meta {
|
|||||||
pub span: Option<syn::Expr>,
|
pub span: Option<syn::Expr>,
|
||||||
pub callsite: Option<syn::Expr>,
|
pub callsite: Option<syn::Expr>,
|
||||||
pub func: Option<syn::Expr>,
|
pub func: Option<syn::Expr>,
|
||||||
pub extras: Vec<(String, Mode, syn::Expr)>,
|
pub extras: Vec<(syn::Ident, Mode, syn::Expr)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Meta {
|
impl Parse for Meta {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
Ok(Self {
|
let out = Self {
|
||||||
name: parse_string::<kw::name>(input)?,
|
name: parse_string::<kw::name>(input)?,
|
||||||
span: parse_key_value::<kw::span, syn::Expr>(input)?,
|
span: parse_key_value::<kw::span, syn::Expr>(input)?,
|
||||||
callsite: parse_key_value::<kw::callsite, syn::Expr>(input)?,
|
callsite: parse_key_value::<kw::callsite, syn::Expr>(input)?,
|
||||||
@ -39,11 +41,26 @@ impl Parse for Meta {
|
|||||||
let value = input.parse()?;
|
let value = input.parse()?;
|
||||||
eat_comma(input);
|
eat_comma(input);
|
||||||
|
|
||||||
pairs.push((key.to_string(), mode, value));
|
pairs.push((key, mode, value));
|
||||||
}
|
}
|
||||||
pairs
|
pairs
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
let mut keys = HashSet::new();
|
||||||
|
keys.insert("name".to_string());
|
||||||
|
keys.insert("span".to_string());
|
||||||
|
keys.insert("callsite".to_string());
|
||||||
|
keys.insert("func".to_string());
|
||||||
|
|
||||||
|
// Check that the keys are unique.
|
||||||
|
for (key, _, _) in &out.extras {
|
||||||
|
if !keys.insert(key.to_string()) {
|
||||||
|
bail!(key, "Duplicate key in #[time(..)]: `{}`", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +115,7 @@ fn create(meta: Meta, mut item: syn::ItemFn) -> Result<TokenStream> {
|
|||||||
Mode::Serialize => (format_ident!("with_arg"), None),
|
Mode::Serialize => (format_ident!("with_arg"), None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let key = key.to_string();
|
||||||
extras.push(quote! { .#method(#key, (#value) #transform) });
|
extras.push(quote! { .#method(#key, (#value) #transform) });
|
||||||
if matches!(mode, Mode::Serialize) {
|
if matches!(mode, Mode::Serialize) {
|
||||||
let error_msg = format!("failed to serialize {key}");
|
let error_msg = format!("failed to serialize {key}");
|
||||||
|
@ -13,7 +13,7 @@ keywords = { workspace = true }
|
|||||||
readme = { workspace = true }
|
readme = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
indexmap = { workspace = true }
|
ecow = { workspace = true }
|
||||||
parking_lot = { workspace = true }
|
parking_lot = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
@ -6,11 +6,10 @@ use std::io::Write;
|
|||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
use std::ops::Not;
|
use std::ops::Not;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use ecow::EcoVec;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::ser::SerializeSeq;
|
use serde::ser::{SerializeMap, SerializeSeq};
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
|
|
||||||
/// Creates a timing scope around an expression.
|
/// Creates a timing scope around an expression.
|
||||||
@ -105,8 +104,11 @@ pub fn export_json<W: Write>(
|
|||||||
ts: f64,
|
ts: f64,
|
||||||
pid: u64,
|
pid: u64,
|
||||||
tid: u64,
|
tid: u64,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(
|
||||||
args: Option<IndexMap<Cow<'a, str>, Cow<'a, serde_json::Value>>>,
|
skip_serializing_if = "Option::is_none",
|
||||||
|
serialize_with = "serialize_vec_as_map"
|
||||||
|
)]
|
||||||
|
args: Option<EcoVec<(Cow<'a, str>, Cow<'a, serde_json::Value>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let lock = EVENTS.lock();
|
let lock = EVENTS.lock();
|
||||||
@ -118,9 +120,9 @@ pub fn export_json<W: Write>(
|
|||||||
.map_err(|e| format!("failed to serialize events: {e}"))?;
|
.map_err(|e| format!("failed to serialize events: {e}"))?;
|
||||||
|
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
let mut args = IndexMap::new();
|
let mut args = EcoVec::with_capacity(event.arguments.len() * 2 + 1);
|
||||||
if let Some(func) = event.func.as_ref() {
|
if let Some(func) = event.func.as_ref() {
|
||||||
args.insert("func".into(), Cow::Owned(serde_json::json!(func)));
|
args.push(("func".into(), Cow::Owned(serde_json::json!(func))));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, arg) in event.arguments.iter() {
|
for (key, arg) in event.arguments.iter() {
|
||||||
@ -143,7 +145,7 @@ pub fn export_json<W: Write>(
|
|||||||
.map_err(|e| format!("failed to serialize event: {e}"))?;
|
.map_err(|e| format!("failed to serialize event: {e}"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
seq.end().map_err(|e| format!("failed to serialize events: {e}"))?;
|
SerializeSeq::end(seq).map_err(|e| format!("failed to serialize events: {e}"))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -153,7 +155,7 @@ pub fn export_json<W: Write>(
|
|||||||
pub struct TimingScope {
|
pub struct TimingScope {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
func: Option<String>,
|
func: Option<String>,
|
||||||
args: IndexMap<&'static str, EventArgument>,
|
args: EcoVec<(&'static str, EventArgument)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimingScope {
|
impl TimingScope {
|
||||||
@ -161,7 +163,7 @@ impl TimingScope {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(name: &'static str) -> Option<Self> {
|
pub fn new(name: &'static str) -> Option<Self> {
|
||||||
if is_enabled() {
|
if is_enabled() {
|
||||||
Some(Self { name, func: None, args: IndexMap::new() })
|
Some(Self { name, func: None, args: EcoVec::new() })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -173,17 +175,17 @@ impl TimingScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_span(mut self, span: NonZeroU64) -> Self {
|
pub fn with_span(mut self, span: NonZeroU64) -> Self {
|
||||||
self.args.insert("span", EventArgument::Span(span));
|
self.args.push(("span", EventArgument::Span(span)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_callsite(mut self, callsite: NonZeroU64) -> Self {
|
pub fn with_callsite(mut self, callsite: NonZeroU64) -> Self {
|
||||||
self.args.insert("callsite", EventArgument::Span(callsite));
|
self.args.push(("callsite", EventArgument::Span(callsite)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_named_span(mut self, name: &'static str, span: NonZeroU64) -> Self {
|
pub fn with_named_span(mut self, name: &'static str, span: NonZeroU64) -> Self {
|
||||||
self.args.insert(name, EventArgument::Span(span));
|
self.args.push((name, EventArgument::Span(span)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +205,7 @@ impl TimingScope {
|
|||||||
value: impl Serialize,
|
value: impl Serialize,
|
||||||
) -> Result<Self, serde_json::Error> {
|
) -> Result<Self, serde_json::Error> {
|
||||||
let value = serde_json::to_value(value)?;
|
let value = serde_json::to_value(value)?;
|
||||||
self.args.insert(arg, EventArgument::Value(value));
|
self.args.push((arg, EventArgument::Value(value)));
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +219,7 @@ impl TimingScope {
|
|||||||
name: self.name,
|
name: self.name,
|
||||||
func: self.func.clone(),
|
func: self.func.clone(),
|
||||||
thread_id,
|
thread_id,
|
||||||
arguments: Arc::new(self.args),
|
arguments: self.args.clone(),
|
||||||
};
|
};
|
||||||
EVENTS.lock().push(event.clone());
|
EVENTS.lock().push(event.clone());
|
||||||
TimingScopeGuard { scope: Some(event) }
|
TimingScopeGuard { scope: Some(event) }
|
||||||
@ -240,6 +242,7 @@ impl Drop for TimingScopeGuard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
enum EventArgument {
|
enum EventArgument {
|
||||||
Span(NonZeroU64),
|
Span(NonZeroU64),
|
||||||
Value(serde_json::Value),
|
Value(serde_json::Value),
|
||||||
@ -250,45 +253,45 @@ impl EventArgument {
|
|||||||
&'a self,
|
&'a self,
|
||||||
mut source: impl FnMut(NonZeroU64) -> (String, u32),
|
mut source: impl FnMut(NonZeroU64) -> (String, u32),
|
||||||
key: &'static str,
|
key: &'static str,
|
||||||
out: &mut IndexMap<Cow<'static, str>, Cow<'a, serde_json::Value>>,
|
out: &mut EcoVec<(Cow<'static, str>, Cow<'a, serde_json::Value>)>,
|
||||||
) -> Result<(), serde_json::Error> {
|
) -> Result<(), serde_json::Error> {
|
||||||
match self {
|
match self {
|
||||||
EventArgument::Span(span) => {
|
EventArgument::Span(span) => {
|
||||||
let (file, line) = source(*span);
|
let (file, line) = source(*span);
|
||||||
// Insert file and line information for the span
|
// Insert file and line information for the span
|
||||||
if key == "span" {
|
if key == "span" {
|
||||||
out.insert("file".into(), Cow::Owned(serde_json::json!(file)));
|
out.push(("file".into(), Cow::Owned(serde_json::json!(file))));
|
||||||
out.insert("line".into(), Cow::Owned(serde_json::json!(line)));
|
out.push(("line".into(), Cow::Owned(serde_json::json!(line))));
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small optimization for callsite
|
// Small optimization for callsite
|
||||||
if key == "callsite" {
|
if key == "callsite" {
|
||||||
out.insert(
|
out.push((
|
||||||
"callsite_file".into(),
|
"callsite_file".into(),
|
||||||
Cow::Owned(serde_json::json!(file)),
|
Cow::Owned(serde_json::json!(file)),
|
||||||
);
|
));
|
||||||
out.insert(
|
out.push((
|
||||||
"callsite_line".into(),
|
"callsite_line".into(),
|
||||||
Cow::Owned(serde_json::json!(line)),
|
Cow::Owned(serde_json::json!(line)),
|
||||||
);
|
));
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
out.insert(
|
out.push((
|
||||||
format!("{key}_file").into(),
|
format!("{key}_file").into(),
|
||||||
Cow::Owned(serde_json::json!(file)),
|
Cow::Owned(serde_json::json!(file)),
|
||||||
);
|
));
|
||||||
|
|
||||||
out.insert(
|
out.push((
|
||||||
format!("{key}_line").into(),
|
format!("{key}_line").into(),
|
||||||
Cow::Owned(serde_json::json!(line)),
|
Cow::Owned(serde_json::json!(line)),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
EventArgument::Value(value) => {
|
EventArgument::Value(value) => {
|
||||||
out.insert(key.into(), Cow::Borrowed(value));
|
out.push((key.into(), Cow::Borrowed(value)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,7 +309,7 @@ struct Event {
|
|||||||
/// The name of this event.
|
/// The name of this event.
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
/// The additional arguments of this event.
|
/// The additional arguments of this event.
|
||||||
arguments: Arc<IndexMap<&'static str, EventArgument>>,
|
arguments: EcoVec<(&'static str, EventArgument)>,
|
||||||
/// The function being called (if any).
|
/// The function being called (if any).
|
||||||
func: Option<String>,
|
func: Option<String>,
|
||||||
/// The thread ID of this event.
|
/// The thread ID of this event.
|
||||||
@ -413,3 +416,24 @@ impl WasmTimer {
|
|||||||
self.time_origin + self.perf.now()
|
self.time_origin + self.perf.now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom serialization function for handling `EcoVec` as a map in JSON.
|
||||||
|
fn serialize_vec_as_map<S>(
|
||||||
|
data: &Option<EcoVec<(Cow<str>, Cow<serde_json::Value>)>>,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let Some(data) = data.as_deref() else {
|
||||||
|
// Should not happen, but if it does, we turn it into a `null` value.
|
||||||
|
return serializer.serialize_none();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut map = serializer.serialize_map(Some(data.len()))?;
|
||||||
|
for (key, value) in data {
|
||||||
|
map.serialize_entry(key, value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user