From a9fdff244aef859449a76e5f762ee7c343a8ddcc Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 10 Mar 2023 20:47:23 +0100 Subject: Expose content representation more --- src/eval/args.rs | 16 +-- src/eval/array.rs | 49 ++++--- src/eval/dict.rs | 45 +++--- src/eval/func.rs | 56 +++----- src/eval/methods.rs | 12 +- src/eval/mod.rs | 16 +-- src/eval/ops.rs | 1 + src/eval/value.rs | 7 +- src/ide/tooltip.rs | 40 +++--- src/model/content.rs | 392 +++++++++++++++++++++++++-------------------------- src/model/realize.rs | 20 +-- src/model/styles.rs | 71 +++++++--- src/util/mod.rs | 76 ++++++---- 13 files changed, 416 insertions(+), 385 deletions(-) (limited to 'src') diff --git a/src/eval/args.rs b/src/eval/args.rs index 159e9a77..17bd9cb4 100644 --- a/src/eval/args.rs +++ b/src/eval/args.rs @@ -1,10 +1,11 @@ -use std::fmt::{self, Debug, Formatter, Write}; +use std::fmt::{self, Debug, Formatter}; -use ecow::EcoVec; +use ecow::{eco_format, EcoVec}; use super::{Array, Cast, Dict, Str, Value}; use crate::diag::{bail, At, SourceResult}; use crate::syntax::{Span, Spanned}; +use crate::util::pretty_array; /// Evaluated arguments to a function. #[derive(Clone, PartialEq, Hash)] @@ -171,14 +172,9 @@ impl Args { impl Debug for Args { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('(')?; - for (i, arg) in self.items.iter().enumerate() { - arg.fmt(f)?; - if i + 1 < self.items.len() { - f.write_str(", ")?; - } - } - f.write_char(')') + let pieces: Vec<_> = + self.items.iter().map(|arg| eco_format!("{arg:?}")).collect(); + f.write_str(&pretty_array(&pieces, false)) } } diff --git a/src/eval/array.rs b/src/eval/array.rs index 53bae06f..8da9b3d2 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -1,11 +1,12 @@ use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter, Write}; +use std::fmt::{self, Debug, Formatter}; use std::ops::{Add, AddAssign}; use ecow::{eco_format, EcoString, EcoVec}; use super::{ops, Args, Func, Value, Vm}; use crate::diag::{bail, At, SourceResult, StrResult}; +use crate::util::pretty_array; /// Create a new [`Array`] from values. #[macro_export] @@ -147,7 +148,6 @@ impl Array { return Ok(Some(item.clone())); } } - Ok(None) } @@ -262,6 +262,14 @@ impl Array { self.0.iter().cloned().rev().collect() } + /// Split all values in the array. + pub fn split(&self, at: Value) -> Array { + self.as_slice() + .split(|value| *value == at) + .map(|subslice| Value::Array(subslice.iter().cloned().collect())) + .collect() + } + /// Join all values in the array, optionally with separator and last /// separator (between the final two items). pub fn join(&self, sep: Option, mut last: Option) -> StrResult { @@ -332,31 +340,10 @@ impl Array { } } -/// The out of bounds access error message. -#[cold] -fn out_of_bounds(index: i64, len: i64) -> EcoString { - eco_format!("array index out of bounds (index: {}, len: {})", index, len) -} - -/// The error message when the array is empty. -#[cold] -fn array_is_empty() -> EcoString { - "array is empty".into() -} - impl Debug for Array { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('(')?; - for (i, value) in self.iter().enumerate() { - value.fmt(f)?; - if i + 1 < self.0.len() { - f.write_str(", ")?; - } - } - if self.len() == 1 { - f.write_char(',')?; - } - f.write_char(')') + let pieces: Vec<_> = self.iter().map(|value| eco_format!("{value:?}")).collect(); + f.write_str(&pretty_array(&pieces, self.len() == 1)) } } @@ -404,3 +391,15 @@ impl<'a> IntoIterator for &'a Array { self.iter() } } + +/// The error message when the array is empty. +#[cold] +fn array_is_empty() -> EcoString { + "array is empty".into() +} + +/// The out of bounds access error message. +#[cold] +fn out_of_bounds(index: i64, len: i64) -> EcoString { + eco_format!("array index out of bounds (index: {}, len: {})", index, len) +} diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 6c1934c9..ececce07 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter, Write}; +use std::fmt::{self, Debug, Formatter}; use std::ops::{Add, AddAssign}; use std::sync::Arc; @@ -8,7 +8,7 @@ use ecow::{eco_format, EcoString}; use super::{array, Array, Str, Value}; use crate::diag::StrResult; use crate::syntax::is_ident; -use crate::util::ArcExt; +use crate::util::{pretty_array, ArcExt}; /// Create a new [`Dict`] from key-value pairs. #[macro_export] @@ -132,31 +132,24 @@ impl Dict { } } -/// The missing key access error message. -#[cold] -fn missing_key(key: &str) -> EcoString { - eco_format!("dictionary does not contain key {:?}", Str::from(key)) -} - impl Debug for Dict { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('(')?; if self.is_empty() { - f.write_char(':')?; + return f.write_str("(:)"); } - for (i, (key, value)) in self.iter().enumerate() { - if is_ident(key) { - f.write_str(key)?; - } else { - write!(f, "{key:?}")?; - } - f.write_str(": ")?; - value.fmt(f)?; - if i + 1 < self.0.len() { - f.write_str(", ")?; - } - } - f.write_char(')') + + let pieces: Vec<_> = self + .iter() + .map(|(key, value)| { + if is_ident(key) { + eco_format!("{key}: {value:?}") + } else { + eco_format!("{key:?}: {value:?}") + } + }) + .collect(); + + f.write_str(&pretty_array(&pieces, false)) } } @@ -207,3 +200,9 @@ impl<'a> IntoIterator for &'a Dict { self.iter() } } + +/// The missing key access error message. +#[cold] +fn missing_key(key: &str) -> EcoString { + eco_format!("dictionary does not contain key {:?}", Str::from(key)) +} diff --git a/src/eval/func.rs b/src/eval/func.rs index 79ae142c..8da5c6bc 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -13,7 +13,7 @@ use super::{ Vm, }; use crate::diag::{bail, SourceResult, StrResult}; -use crate::model::{Content, NodeId, Selector, StyleMap}; +use crate::model::{NodeId, Selector, StyleMap}; use crate::syntax::ast::{self, AstNode, Expr}; use crate::syntax::{SourceId, Span, SyntaxNode}; use crate::util::hash128; @@ -29,7 +29,7 @@ enum Repr { /// A native Rust function. Native(NativeFunc), /// A function for a node. - Node(NodeFunc), + Node(NodeId), /// A user-defined closure. Closure(Closure), /// A nested function with pre-applied arguments. @@ -156,13 +156,13 @@ impl Func { /// Create a selector for this function's node type. pub fn select(&self, fields: Option) -> StrResult { - match &**self.0 { - Repr::Node(node) => { - if node.id == item!(text_id) { + match **self.0 { + Repr::Node(id) => { + if id == item!(text_id) { Err("to select text, please use a string or regex instead")?; } - Ok(Selector::Node(node.id, fields)) + Ok(Selector::Node(id, fields)) } _ => Err("this function is not selectable")?, } @@ -172,8 +172,8 @@ impl Func { impl Debug for Func { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self.name() { - Some(name) => write!(f, ""), - None => f.write_str(""), + Some(name) => write!(f, "{name}"), + None => f.write_str("(..) => .."), } } } @@ -190,6 +190,16 @@ impl From for Func { } } +impl From for Func { + fn from(id: NodeId) -> Self { + Repr::Node(id).into() + } +} + +cast_to_value! { + v: NodeId => Value::Func(v.into()) +} + /// A native Rust function. pub struct NativeFunc { /// The function's implementation. @@ -223,36 +233,6 @@ where } } -/// A function defined by a native Rust node. -pub struct NodeFunc { - /// The node's id. - pub id: NodeId, - /// The node's constructor. - pub construct: fn(&Vm, &mut Args) -> SourceResult, - /// The node's set rule. - pub set: fn(&mut Args) -> SourceResult, - /// Details about the function. - pub info: Lazy, -} - -impl Hash for NodeFunc { - fn hash(&self, state: &mut H) { - self.id.hash(state); - (self.construct as usize).hash(state); - (self.set as usize).hash(state); - } -} - -impl From for Func { - fn from(node: NodeFunc) -> Self { - Repr::Node(node).into() - } -} - -cast_to_value! { - v: NodeFunc => Value::Func(v.into()) -} - /// Details about a function. #[derive(Debug, Clone)] pub struct FuncInfo { diff --git a/src/eval/methods.rs b/src/eval/methods.rs index dcb1ca31..197a2f65 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -69,6 +69,13 @@ pub fn call( _ => return missing(), }, + Value::Content(content) => match method { + "func" => Value::Func(content.id().into()), + "has" => Value::Bool(content.has(&args.expect::("field")?)), + "at" => content.at(&args.expect::("field")?).at(span)?.clone(), + _ => return missing(), + }, + Value::Array(array) => match method { "len" => Value::Int(array.len()), "first" => array.first().at(span)?.clone(), @@ -96,6 +103,7 @@ pub fn call( "all" => Value::Bool(array.all(vm, args.expect("function")?)?), "flatten" => Value::Array(array.flatten()), "rev" => Value::Array(array.rev()), + "split" => Value::Array(array.split(args.expect("separator")?)), "join" => { let sep = args.eat()?; let last = args.named("last")?; @@ -107,7 +115,7 @@ pub fn call( Value::Dict(dict) => match method { "len" => Value::Int(dict.len()), - "at" => dict.at(&args.expect::("key")?).cloned().at(span)?, + "at" => dict.at(&args.expect::("key")?).at(span)?.clone(), "keys" => Value::Array(dict.keys()), "values" => Value::Array(dict.values()), "pairs" => Value::Array(dict.pairs()), @@ -237,6 +245,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ("starts-with", true), ("trim", true), ], + "content" => &[("func", false), ("has", true), ("at", true)], "array" => &[ ("all", true), ("any", true), @@ -248,6 +257,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ("flatten", false), ("fold", true), ("insert", true), + ("split", true), ("join", true), ("last", false), ("len", false), diff --git a/src/eval/mod.rs b/src/eval/mod.rs index fcfda263..145f961a 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -47,6 +47,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{ bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, }; +use crate::model::Unlabellable; use crate::model::{Content, Label, Recipe, Selector, StyleMap, Transform}; use crate::syntax::ast::AstNode; use crate::syntax::{ @@ -357,12 +358,12 @@ fn eval_markup( } let tail = eval_markup(vm, exprs)?; - seq.push(tail.apply_recipe(vm.world, recipe)?) + seq.push(tail.styled_with_recipe(vm.world, recipe)?) } expr => match expr.eval(vm)? { Value::Label(label) => { if let Some(node) = - seq.iter_mut().rev().find(|node| node.labellable()) + seq.iter_mut().rev().find(|node| !node.can::()) { *node = mem::take(node).labelled(label); } @@ -786,7 +787,7 @@ fn eval_code( } let tail = eval_code(vm, exprs)?.display(); - Value::Content(tail.apply_recipe(vm.world, recipe)?) + Value::Content(tail.styled_with_recipe(vm.world, recipe)?) } _ => expr.eval(vm)?, }; @@ -979,6 +980,10 @@ impl Eval for ast::FuncCall { fn eval(&self, vm: &mut Vm) -> SourceResult { let span = self.span(); + if vm.depth >= MAX_CALL_DEPTH { + bail!(span, "maximum function call depth exceeded"); + } + let callee = self.callee(); let in_math = in_math(&callee); let callee_span = callee.span(); @@ -1042,11 +1047,6 @@ impl Eval for ast::FuncCall { )); } - // Finally, just a normal function call! - if vm.depth >= MAX_CALL_DEPTH { - bail!(span, "maximum function call depth exceeded"); - } - let callee = callee.cast::().at(callee_span)?; let point = || Tracepoint::Call(callee.name().map(Into::into)); callee.call(vm, args).trace(vm.world, point, span) diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 52b9b06a..5efb68c3 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -340,6 +340,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool { (Symbol(a), Symbol(b)) => a == b, (Str(a), Str(b)) => a == b, (Label(a), Label(b)) => a == b, + (Content(a), Content(b)) => a == b, (Array(a), Array(b)) => a == b, (Dict(a), Dict(b)) => a == b, (Func(a), Func(b)) => a == b, diff --git a/src/eval/value.rs b/src/eval/value.rs index 9b9bc314..61af36f5 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -120,10 +120,7 @@ impl Value { match self { Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol), Self::Dict(dict) => dict.at(&field).cloned(), - Self::Content(content) => content - .field(&field) - .cloned() - .ok_or_else(|| eco_format!("unknown field `{field}`")), + Self::Content(content) => content.at(&field).cloned(), Self::Module(module) => module.get(&field).cloned(), v => Err(eco_format!("cannot access fields on type {}", v.type_name())), } @@ -190,7 +187,7 @@ impl Debug for Value { Self::Symbol(v) => Debug::fmt(v, f), Self::Str(v) => Debug::fmt(v, f), Self::Label(v) => Debug::fmt(v, f), - Self::Content(_) => f.pad("[...]"), + Self::Content(v) => Debug::fmt(v, f), Self::Array(v) => Debug::fmt(v, f), Self::Dict(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), diff --git a/src/ide/tooltip.rs b/src/ide/tooltip.rs index ac0cb60b..4f281050 100644 --- a/src/ide/tooltip.rs +++ b/src/ide/tooltip.rs @@ -1,11 +1,15 @@ +use std::fmt::Write; + +use ecow::EcoString; + use if_chain::if_chain; -use unicode_segmentation::UnicodeSegmentation; use super::{analyze_expr, plain_docs_sentence, summarize_font_family}; use crate::eval::{CastInfo, Tracer, Value}; use crate::geom::{round_2, Length, Numeric}; use crate::syntax::ast; use crate::syntax::{LinkedNode, Source, SyntaxKind}; +use crate::util::pretty_comma_list; use crate::World; /// Describe the item under the cursor. @@ -60,31 +64,27 @@ fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option 0 && !tooltip.is_empty() { - tooltip.push_str(", "); - } - let repr = value.repr(); - let repr = repr.as_str(); - let len = repr.len(); - if len <= 40 { - tooltip.push_str(repr); - } else { - let mut graphemes = repr.graphemes(true); - let r = graphemes.next_back().map_or(0, str::len); - let l = graphemes.take(40).map(str::len).sum(); - tooltip.push_str(&repr[..l]); - tooltip.push_str("..."); - tooltip.push_str(&repr[len - r..]); + let mut last = None; + let mut pieces: Vec = vec![]; + let mut iter = values.iter(); + for value in (&mut iter).take(Tracer::MAX - 1) { + if let Some((prev, count)) = &mut last { + if *prev == value { + *count += 1; + continue; + } else if *count > 1 { + write!(pieces.last_mut().unwrap(), " (x{count})").unwrap(); + } } + pieces.push(value.repr().into()); + last = Some((value, 1)); } if iter.next().is_some() { - tooltip.push_str(", ..."); + pieces.push("...".into()); } + let tooltip = pretty_comma_list(&pieces, false); (!tooltip.is_empty()).then(|| Tooltip::Code(tooltip)) } diff --git a/src/model/content.rs b/src/model/content.rs index d845ce1e..b895553c 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -2,22 +2,24 @@ use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::iter::{self, Sum}; -use std::ops::{Add, AddAssign}; +use std::ops::{Add, AddAssign, Deref}; use comemo::Tracked; -use ecow::{EcoString, EcoVec}; +use ecow::{eco_format, EcoString, EcoVec}; +use once_cell::sync::Lazy; use super::{node, Guard, Recipe, Style, StyleMap}; use crate::diag::{SourceResult, StrResult}; -use crate::eval::{cast_from_value, Args, Cast, NodeFunc, Value, Vm}; +use crate::eval::{cast_from_value, Args, FuncInfo, Str, Value, Vm}; use crate::syntax::Span; +use crate::util::pretty_array; use crate::World; /// Composable representation of styled content. #[derive(Clone, Hash)] pub struct Content { id: NodeId, - span: Option, + span: Span, fields: EcoVec<(EcoString, Value)>, modifiers: EcoVec, } @@ -33,7 +35,7 @@ impl Content { pub fn new() -> Self { Self { id: T::id(), - span: None, + span: Span::detached(), fields: EcoVec::new(), modifiers: EcoVec::new(), } @@ -52,82 +54,67 @@ impl Content { } } - /// Attach a span to the content. - pub fn spanned(mut self, span: Span) -> Self { - if let Some(styled) = self.to::() { - self = StyledNode::new(styled.map(), styled.body().spanned(span)).pack(); - } - self.span = Some(span); - self - } - - /// Attach a label to the content. - pub fn labelled(self, label: Label) -> Self { - self.with_field("label", label) + /// The id of the contained node. + pub fn id(&self) -> NodeId { + self.id } - /// Style this content with a style entry. - pub fn styled(self, style: impl Into