mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Tracing-based expression tooltips
This commit is contained in:
parent
c56299c6bd
commit
43ef60c09c
@ -112,6 +112,7 @@ pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
|||||||
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
|
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
|
||||||
let source = Source::synthesized(text, span);
|
let source = Source::synthesized(text, span);
|
||||||
let route = model::Route::default();
|
let route = model::Route::default();
|
||||||
let module = model::eval(vm.world(), route.track(), &source)?;
|
let mut tracer = model::Tracer::default();
|
||||||
|
let module = model::eval(vm.world(), route.track(), tracer.track_mut(), &source)?;
|
||||||
Ok(Value::Content(module.content()))
|
Ok(Value::Content(module.content()))
|
||||||
}
|
}
|
||||||
|
38
src/ide/analyze.rs
Normal file
38
src/ide/analyze.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use comemo::Track;
|
||||||
|
|
||||||
|
use crate::model::{eval, Route, Tracer, Value};
|
||||||
|
use crate::syntax::{ast, LinkedNode, SyntaxKind};
|
||||||
|
use crate::World;
|
||||||
|
|
||||||
|
/// Try to determine a set of possible values for an expression.
|
||||||
|
pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
|
||||||
|
match node.cast::<ast::Expr>() {
|
||||||
|
Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_)) => {
|
||||||
|
if let Some(parent) = node.parent() {
|
||||||
|
if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
|
||||||
|
return analyze(world, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = node.span();
|
||||||
|
let source = world.source(span.source());
|
||||||
|
let route = Route::default();
|
||||||
|
let mut tracer = Tracer::new(Some(span));
|
||||||
|
eval(world.track(), route.track(), tracer.track_mut(), source).ok();
|
||||||
|
return tracer.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ast::Expr::FieldAccess(access)) => {
|
||||||
|
if let Some(child) = node.children().next() {
|
||||||
|
return analyze(world, &child)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|target| target.field(&access.field()).ok())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![]
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
//! Capabilities for IDE support.
|
//! Capabilities for IDE support.
|
||||||
|
|
||||||
|
mod analyze;
|
||||||
mod complete;
|
mod complete;
|
||||||
mod highlight;
|
mod highlight;
|
||||||
mod tooltip;
|
mod tooltip;
|
||||||
|
|
||||||
|
pub use analyze::*;
|
||||||
pub use complete::*;
|
pub use complete::*;
|
||||||
pub use highlight::*;
|
pub use highlight::*;
|
||||||
pub use tooltip::*;
|
pub use tooltip::*;
|
||||||
|
@ -1,40 +1,71 @@
|
|||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::{plain_docs_sentence, summarize_font_family};
|
use super::{analyze, plain_docs_sentence, summarize_font_family};
|
||||||
use crate::model::{CastInfo, Value};
|
use crate::model::{CastInfo, Tracer, Value};
|
||||||
use crate::syntax::ast;
|
use crate::syntax::ast;
|
||||||
use crate::syntax::{LinkedNode, Source, SyntaxKind};
|
use crate::syntax::{LinkedNode, Source, SyntaxKind};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Describe the item under the cursor.
|
/// Describe the item under the cursor.
|
||||||
pub fn tooltip(world: &dyn World, source: &Source, cursor: usize) -> Option<String> {
|
pub fn tooltip(
|
||||||
|
world: &(dyn World + 'static),
|
||||||
|
source: &Source,
|
||||||
|
cursor: usize,
|
||||||
|
) -> Option<String> {
|
||||||
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
|
|
||||||
function_tooltip(world, &leaf)
|
named_param_tooltip(world, &leaf)
|
||||||
.or_else(|| named_param_tooltip(world, &leaf))
|
|
||||||
.or_else(|| font_family_tooltip(world, &leaf))
|
.or_else(|| font_family_tooltip(world, &leaf))
|
||||||
|
.or_else(|| expr_tooltip(world, &leaf))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tooltip for a function or set rule name.
|
/// Tooltip for a hovered expression.
|
||||||
fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
|
fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<String> {
|
||||||
if_chain! {
|
if !leaf.is::<ast::Expr>() {
|
||||||
if let Some(ident) = leaf.cast::<ast::Ident>();
|
return None;
|
||||||
if matches!(
|
}
|
||||||
leaf.parent_kind(),
|
|
||||||
Some(SyntaxKind::FuncCall | SyntaxKind::SetRule),
|
let values = analyze(world, leaf);
|
||||||
);
|
if let [value] = values.as_slice() {
|
||||||
if let Some(Value::Func(func)) = world.library().global.scope().get(&ident);
|
if let Some(docs) = value.docs() {
|
||||||
if let Some(info) = func.info();
|
return Some(plain_docs_sentence(docs));
|
||||||
then {
|
|
||||||
return Some(plain_docs_sentence(info.docs));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
let mut tooltip = String::new();
|
||||||
|
let mut iter = values.into_iter().enumerate();
|
||||||
|
for (i, value) in (&mut iter).take(Tracer::MAX - 1) {
|
||||||
|
if i > 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..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if iter.next().is_some() {
|
||||||
|
tooltip.push_str(", ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
(!tooltip.is_empty()).then(|| tooltip)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tooltips for components of a named parameter.
|
/// Tooltips for components of a named parameter.
|
||||||
fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
|
fn named_param_tooltip(
|
||||||
|
world: &(dyn World + 'static),
|
||||||
|
leaf: &LinkedNode,
|
||||||
|
) -> Option<String> {
|
||||||
let (info, named) = if_chain! {
|
let (info, named) = if_chain! {
|
||||||
// Ensure that we are in a named pair in the arguments to a function
|
// Ensure that we are in a named pair in the arguments to a function
|
||||||
// call or set rule.
|
// call or set rule.
|
||||||
@ -92,7 +123,10 @@ fn find_string_doc(info: &CastInfo, string: &str) -> Option<&'static str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Tooltip for font family.
|
/// Tooltip for font family.
|
||||||
fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
|
fn font_family_tooltip(
|
||||||
|
world: &(dyn World + 'static),
|
||||||
|
leaf: &LinkedNode,
|
||||||
|
) -> Option<String> {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
// Ensure that we are on top of a string.
|
// Ensure that we are on top of a string.
|
||||||
if let Some(string) = leaf.cast::<ast::Str>();
|
if let Some(string) = leaf.cast::<ast::Str>();
|
||||||
|
@ -51,6 +51,7 @@ pub mod syntax;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use comemo::{Prehashed, Track};
|
use comemo::{Prehashed, Track};
|
||||||
|
use model::Tracer;
|
||||||
|
|
||||||
use crate::diag::{FileResult, SourceResult};
|
use crate::diag::{FileResult, SourceResult};
|
||||||
use crate::doc::Document;
|
use crate::doc::Document;
|
||||||
@ -63,7 +64,8 @@ use crate::util::Buffer;
|
|||||||
pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult<Document> {
|
pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult<Document> {
|
||||||
// Evaluate the source file into a module.
|
// Evaluate the source file into a module.
|
||||||
let route = Route::default();
|
let route = Route::default();
|
||||||
let module = model::eval(world.track(), route.track(), source)?;
|
let mut tracer = Tracer::default();
|
||||||
|
let module = model::eval(world.track(), route.track(), tracer.track_mut(), source)?;
|
||||||
|
|
||||||
// Typeset the module's contents.
|
// Typeset the module's contents.
|
||||||
model::typeset(world.track(), &module.content())
|
model::typeset(world.track(), &module.content())
|
||||||
|
@ -136,7 +136,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the first matching element.
|
/// Return the first matching element.
|
||||||
pub fn find(&self, vm: &Vm, func: Func) -> SourceResult<Option<Value>> {
|
pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<Value>> {
|
||||||
if func.argc().map_or(false, |count| count != 1) {
|
if func.argc().map_or(false, |count| count != 1) {
|
||||||
bail!(func.span(), "function must have exactly one parameter");
|
bail!(func.span(), "function must have exactly one parameter");
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the index of the first matching element.
|
/// Return the index of the first matching element.
|
||||||
pub fn position(&self, vm: &Vm, func: Func) -> SourceResult<Option<i64>> {
|
pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<i64>> {
|
||||||
if func.argc().map_or(false, |count| count != 1) {
|
if func.argc().map_or(false, |count| count != 1) {
|
||||||
bail!(func.span(), "function must have exactly one parameter");
|
bail!(func.span(), "function must have exactly one parameter");
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ impl Array {
|
|||||||
|
|
||||||
/// Return a new array with only those elements for which the function
|
/// Return a new array with only those elements for which the function
|
||||||
/// returns true.
|
/// returns true.
|
||||||
pub fn filter(&self, vm: &Vm, func: Func) -> SourceResult<Self> {
|
pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> {
|
||||||
if func.argc().map_or(false, |count| count != 1) {
|
if func.argc().map_or(false, |count| count != 1) {
|
||||||
bail!(func.span(), "function must have exactly one parameter");
|
bail!(func.span(), "function must have exactly one parameter");
|
||||||
}
|
}
|
||||||
@ -182,7 +182,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Transform each item in the array with a function.
|
/// Transform each item in the array with a function.
|
||||||
pub fn map(&self, vm: &Vm, func: Func) -> SourceResult<Self> {
|
pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> {
|
||||||
if func.argc().map_or(false, |count| !(1..=2).contains(&count)) {
|
if func.argc().map_or(false, |count| !(1..=2).contains(&count)) {
|
||||||
bail!(func.span(), "function must have one or two parameters");
|
bail!(func.span(), "function must have one or two parameters");
|
||||||
}
|
}
|
||||||
@ -201,7 +201,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fold all of the array's elements into one with a function.
|
/// Fold all of the array's elements into one with a function.
|
||||||
pub fn fold(&self, vm: &Vm, init: Value, func: Func) -> SourceResult<Value> {
|
pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult<Value> {
|
||||||
if func.argc().map_or(false, |count| count != 2) {
|
if func.argc().map_or(false, |count| count != 2) {
|
||||||
bail!(func.span(), "function must have exactly two parameters");
|
bail!(func.span(), "function must have exactly two parameters");
|
||||||
}
|
}
|
||||||
@ -214,7 +214,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether any element matches.
|
/// Whether any element matches.
|
||||||
pub fn any(&self, vm: &Vm, func: Func) -> SourceResult<bool> {
|
pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> {
|
||||||
if func.argc().map_or(false, |count| count != 1) {
|
if func.argc().map_or(false, |count| count != 1) {
|
||||||
bail!(func.span(), "function must have exactly one parameter");
|
bail!(func.span(), "function must have exactly one parameter");
|
||||||
}
|
}
|
||||||
@ -229,7 +229,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether all elements match.
|
/// Whether all elements match.
|
||||||
pub fn all(&self, vm: &Vm, func: Func) -> SourceResult<bool> {
|
pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> {
|
||||||
if func.argc().map_or(false, |count| count != 1) {
|
if func.argc().map_or(false, |count| count != 1) {
|
||||||
bail!(func.span(), "function must have exactly one parameter");
|
bail!(func.span(), "function must have exactly one parameter");
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Transform each pair in the dictionary with a function.
|
/// Transform each pair in the dictionary with a function.
|
||||||
pub fn map(&self, vm: &Vm, func: Func) -> SourceResult<Array> {
|
pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Array> {
|
||||||
if func.argc().map_or(false, |count| count != 2) {
|
if func.argc().map_or(false, |count| count != 2) {
|
||||||
bail!(func.span(), "function must have exactly two parameters");
|
bail!(func.span(), "function must have exactly two parameters");
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use comemo::{Track, Tracked};
|
use comemo::{Track, Tracked, TrackedMut};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -17,7 +17,7 @@ use crate::diag::{
|
|||||||
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
||||||
use crate::syntax::ast::AstNode;
|
use crate::syntax::ast::AstNode;
|
||||||
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode};
|
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode};
|
||||||
use crate::util::{EcoString, PathExt};
|
use crate::util::PathExt;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
const MAX_ITERATIONS: usize = 10_000;
|
const MAX_ITERATIONS: usize = 10_000;
|
||||||
@ -28,6 +28,7 @@ const MAX_CALL_DEPTH: usize = 256;
|
|||||||
pub fn eval(
|
pub fn eval(
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
route: Tracked<Route>,
|
route: Tracked<Route>,
|
||||||
|
tracer: TrackedMut<Tracer>,
|
||||||
source: &Source,
|
source: &Source,
|
||||||
) -> SourceResult<Module> {
|
) -> SourceResult<Module> {
|
||||||
// Prevent cyclic evaluation.
|
// Prevent cyclic evaluation.
|
||||||
@ -44,7 +45,7 @@ pub fn eval(
|
|||||||
// Evaluate the module.
|
// Evaluate the module.
|
||||||
let route = unsafe { Route::insert(route, id) };
|
let route = unsafe { Route::insert(route, id) };
|
||||||
let scopes = Scopes::new(Some(library));
|
let scopes = Scopes::new(Some(library));
|
||||||
let mut vm = Vm::new(world, route.track(), id, scopes, 0);
|
let mut vm = Vm::new(world, route.track(), tracer, id, scopes, 0);
|
||||||
let result = source.ast()?.eval(&mut vm);
|
let result = source.ast()?.eval(&mut vm);
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
@ -68,6 +69,8 @@ pub struct Vm<'a> {
|
|||||||
pub(super) items: LangItems,
|
pub(super) items: LangItems,
|
||||||
/// The route of source ids the VM took to reach its current location.
|
/// The route of source ids the VM took to reach its current location.
|
||||||
pub(super) route: Tracked<'a, Route>,
|
pub(super) route: Tracked<'a, Route>,
|
||||||
|
/// The tracer for inspection of the values an expression produces.
|
||||||
|
pub(super) tracer: TrackedMut<'a, Tracer>,
|
||||||
/// The current location.
|
/// The current location.
|
||||||
pub(super) location: SourceId,
|
pub(super) location: SourceId,
|
||||||
/// A control flow event that is currently happening.
|
/// A control flow event that is currently happening.
|
||||||
@ -76,6 +79,8 @@ pub struct Vm<'a> {
|
|||||||
pub(super) scopes: Scopes<'a>,
|
pub(super) scopes: Scopes<'a>,
|
||||||
/// The current call depth.
|
/// The current call depth.
|
||||||
pub(super) depth: usize,
|
pub(super) depth: usize,
|
||||||
|
/// A span that is currently traced.
|
||||||
|
pub(super) traced: Option<Span>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Vm<'a> {
|
impl<'a> Vm<'a> {
|
||||||
@ -83,18 +88,22 @@ impl<'a> Vm<'a> {
|
|||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
world: Tracked<'a, dyn World>,
|
world: Tracked<'a, dyn World>,
|
||||||
route: Tracked<'a, Route>,
|
route: Tracked<'a, Route>,
|
||||||
|
tracer: TrackedMut<'a, Tracer>,
|
||||||
location: SourceId,
|
location: SourceId,
|
||||||
scopes: Scopes<'a>,
|
scopes: Scopes<'a>,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let traced = tracer.span(location);
|
||||||
Self {
|
Self {
|
||||||
world,
|
world,
|
||||||
items: world.library().items.clone(),
|
items: world.library().items.clone(),
|
||||||
route,
|
route,
|
||||||
|
tracer,
|
||||||
location,
|
location,
|
||||||
flow: None,
|
flow: None,
|
||||||
scopes,
|
scopes,
|
||||||
depth,
|
depth,
|
||||||
|
traced,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +112,15 @@ impl<'a> Vm<'a> {
|
|||||||
self.world
|
self.world
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Define a variable in the current scope.
|
||||||
|
pub fn define(&mut self, var: ast::Ident, value: impl Into<Value>) {
|
||||||
|
let value = value.into();
|
||||||
|
if self.traced == Some(var.span()) {
|
||||||
|
self.tracer.trace(value.clone());
|
||||||
|
}
|
||||||
|
self.scopes.top.define(var.take(), value);
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve a user-entered path to be relative to the compilation
|
/// Resolve a user-entered path to be relative to the compilation
|
||||||
/// environment's root.
|
/// environment's root.
|
||||||
pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
|
pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
|
||||||
@ -182,6 +200,47 @@ impl Route {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Traces which values existed for the expression with the given span.
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct Tracer {
|
||||||
|
span: Option<Span>,
|
||||||
|
values: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tracer {
|
||||||
|
/// The maximum number of traced items.
|
||||||
|
pub const MAX: usize = 10;
|
||||||
|
|
||||||
|
/// Create a new tracer, possibly with a span under inspection.
|
||||||
|
pub fn new(span: Option<Span>) -> Self {
|
||||||
|
Self { span, values: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the traced values.
|
||||||
|
pub fn finish(self) -> Vec<Value> {
|
||||||
|
self.values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[comemo::track]
|
||||||
|
impl Tracer {
|
||||||
|
/// The traced span if it is part of the given source file.
|
||||||
|
fn span(&self, id: SourceId) -> Option<Span> {
|
||||||
|
if self.span.map(Span::source) == Some(id) {
|
||||||
|
self.span
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trace a value for the span.
|
||||||
|
fn trace(&mut self, v: Value) {
|
||||||
|
if self.values.len() < Self::MAX {
|
||||||
|
self.values.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate an expression.
|
/// Evaluate an expression.
|
||||||
pub(super) trait Eval {
|
pub(super) trait Eval {
|
||||||
/// The output of evaluating the expression.
|
/// The output of evaluating the expression.
|
||||||
@ -259,12 +318,11 @@ impl Eval for ast::Expr {
|
|||||||
error!(span, "{} is only allowed directly in code and content blocks", name)
|
error!(span, "{} is only allowed directly in code and content blocks", name)
|
||||||
};
|
};
|
||||||
|
|
||||||
match self {
|
let v = match self {
|
||||||
Self::Text(v) => v.eval(vm).map(Value::Content),
|
Self::Text(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Space(v) => v.eval(vm).map(Value::Content),
|
Self::Space(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Linebreak(v) => v.eval(vm).map(Value::Content),
|
Self::Linebreak(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Parbreak(v) => v.eval(vm).map(Value::Content),
|
Self::Parbreak(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Symbol(v) => v.eval(vm).map(Value::Content),
|
|
||||||
Self::Escape(v) => v.eval(vm),
|
Self::Escape(v) => v.eval(vm),
|
||||||
Self::Shorthand(v) => v.eval(vm),
|
Self::Shorthand(v) => v.eval(vm),
|
||||||
Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
|
Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
|
||||||
@ -319,6 +377,8 @@ impl Eval for ast::Expr {
|
|||||||
}?
|
}?
|
||||||
.spanned(span);
|
.spanned(span);
|
||||||
|
|
||||||
|
if vm.traced == Some(span) {
|
||||||
|
vm.tracer.trace(v.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(v)
|
Ok(v)
|
||||||
@ -1049,7 +1109,7 @@ impl Eval for ast::LetBinding {
|
|||||||
Some(expr) => expr.eval(vm)?,
|
Some(expr) => expr.eval(vm)?,
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
};
|
};
|
||||||
vm.scopes.top.define(self.binding().take(), value);
|
vm.define(self.binding(), value);
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1183,7 +1243,7 @@ impl Eval for ast::ForLoop {
|
|||||||
|
|
||||||
#[allow(unused_parens)]
|
#[allow(unused_parens)]
|
||||||
for ($($value),*) in $iter {
|
for ($($value),*) in $iter {
|
||||||
$(vm.scopes.top.define($binding.clone(), $value);)*
|
$(vm.define($binding.clone(), $value);)*
|
||||||
|
|
||||||
let body = self.body();
|
let body = self.body();
|
||||||
let value = body.eval(vm)?;
|
let value = body.eval(vm)?;
|
||||||
@ -1206,8 +1266,8 @@ impl Eval for ast::ForLoop {
|
|||||||
|
|
||||||
let iter = self.iter().eval(vm)?;
|
let iter = self.iter().eval(vm)?;
|
||||||
let pattern = self.pattern();
|
let pattern = self.pattern();
|
||||||
let key = pattern.key().map(ast::Ident::take);
|
let key = pattern.key();
|
||||||
let value = pattern.value().take();
|
let value = pattern.value();
|
||||||
|
|
||||||
match (key, value, iter) {
|
match (key, value, iter) {
|
||||||
(None, v, Value::Str(string)) => {
|
(None, v, Value::Str(string)) => {
|
||||||
@ -1271,7 +1331,7 @@ impl Eval for ast::ModuleImport {
|
|||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
for ident in idents {
|
for ident in idents {
|
||||||
if let Some(value) = module.scope().get(&ident) {
|
if let Some(value) = module.scope().get(&ident) {
|
||||||
vm.scopes.top.define(ident.take(), value.clone());
|
vm.define(ident, value.clone());
|
||||||
} else {
|
} else {
|
||||||
errors.push(error!(ident.span(), "unresolved import"));
|
errors.push(error!(ident.span(), "unresolved import"));
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use comemo::{Track, Tracked};
|
use comemo::{Track, Tracked, TrackedMut};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector,
|
Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector,
|
||||||
StyleMap, Value, Vm,
|
StyleMap, Tracer, Value, Vm,
|
||||||
};
|
};
|
||||||
use crate::diag::{bail, SourceResult, StrResult};
|
use crate::diag::{bail, SourceResult, StrResult};
|
||||||
use crate::syntax::ast::{self, AstNode, Expr};
|
use crate::syntax::ast::{self, AstNode, Expr};
|
||||||
@ -110,7 +110,7 @@ impl Func {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call the function with the given arguments.
|
/// Call the function with the given arguments.
|
||||||
pub fn call(&self, vm: &Vm, mut args: Args) -> SourceResult<Value> {
|
pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> {
|
||||||
let value = match self.0.as_ref() {
|
let value = match self.0.as_ref() {
|
||||||
Repr::Native(native) => (native.func)(vm, &mut args)?,
|
Repr::Native(native) => (native.func)(vm, &mut args)?,
|
||||||
Repr::Closure(closure) => closure.call(vm, self, &mut args)?,
|
Repr::Closure(closure) => closure.call(vm, self, &mut args)?,
|
||||||
@ -132,8 +132,9 @@ impl Func {
|
|||||||
let route = Route::default();
|
let route = Route::default();
|
||||||
let id = SourceId::detached();
|
let id = SourceId::detached();
|
||||||
let scopes = Scopes::new(None);
|
let scopes = Scopes::new(None);
|
||||||
let vm = Vm::new(world, route.track(), id, scopes, 0);
|
let mut tracer = Tracer::default();
|
||||||
self.call(&vm, args)
|
let mut vm = Vm::new(world, route.track(), tracer.track_mut(), id, scopes, 0);
|
||||||
|
self.call(&mut vm, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the given arguments to the function.
|
/// Apply the given arguments to the function.
|
||||||
@ -292,7 +293,7 @@ pub(super) struct Closure {
|
|||||||
|
|
||||||
impl Closure {
|
impl Closure {
|
||||||
/// Call the function in the context with the arguments.
|
/// Call the function in the context with the arguments.
|
||||||
fn call(&self, vm: &Vm, this: &Func, args: &mut Args) -> SourceResult<Value> {
|
fn call(&self, vm: &mut Vm, this: &Func, args: &mut Args) -> SourceResult<Value> {
|
||||||
// Don't leak the scopes from the call site. Instead, we use the scope
|
// Don't leak the scopes from the call site. Instead, we use the scope
|
||||||
// of captured variables we collected earlier.
|
// of captured variables we collected earlier.
|
||||||
let mut scopes = Scopes::new(None);
|
let mut scopes = Scopes::new(None);
|
||||||
@ -327,7 +328,14 @@ impl Closure {
|
|||||||
let route = if detached { fresh.track() } else { vm.route };
|
let route = if detached { fresh.track() } else { vm.route };
|
||||||
|
|
||||||
// Evaluate the body.
|
// Evaluate the body.
|
||||||
let mut sub = Vm::new(vm.world, route, self.location, scopes, vm.depth + 1);
|
let mut sub = Vm::new(
|
||||||
|
vm.world,
|
||||||
|
route,
|
||||||
|
TrackedMut::reborrow_mut(&mut vm.tracer),
|
||||||
|
self.location,
|
||||||
|
scopes,
|
||||||
|
vm.depth + 1,
|
||||||
|
);
|
||||||
let result = self.body.eval(&mut sub);
|
let result = self.body.eval(&mut sub);
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
|
@ -7,7 +7,7 @@ use crate::util::EcoString;
|
|||||||
|
|
||||||
/// Call a method on a value.
|
/// Call a method on a value.
|
||||||
pub fn call(
|
pub fn call(
|
||||||
vm: &Vm,
|
vm: &mut Vm,
|
||||||
value: Value,
|
value: Value,
|
||||||
method: &str,
|
method: &str,
|
||||||
mut args: Args,
|
mut args: Args,
|
||||||
|
@ -155,6 +155,14 @@ impl Value {
|
|||||||
_ => self.display(),
|
_ => self.display(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to extract documentation for the value.
|
||||||
|
pub fn docs(&self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Self::Func(func) => func.info().map(|info| info.docs),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Value {
|
impl Default for Value {
|
||||||
|
@ -60,13 +60,29 @@ fn bench_edit(iai: &mut Iai) {
|
|||||||
fn bench_eval(iai: &mut Iai) {
|
fn bench_eval(iai: &mut Iai) {
|
||||||
let world = BenchWorld::new();
|
let world = BenchWorld::new();
|
||||||
let route = typst::model::Route::default();
|
let route = typst::model::Route::default();
|
||||||
iai.run(|| typst::model::eval(world.track(), route.track(), &world.source).unwrap());
|
let mut tracer = typst::model::Tracer::default();
|
||||||
|
iai.run(|| {
|
||||||
|
typst::model::eval(
|
||||||
|
world.track(),
|
||||||
|
route.track(),
|
||||||
|
tracer.track_mut(),
|
||||||
|
&world.source,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_typeset(iai: &mut Iai) {
|
fn bench_typeset(iai: &mut Iai) {
|
||||||
let world = BenchWorld::new();
|
let world = BenchWorld::new();
|
||||||
let route = typst::model::Route::default();
|
let route = typst::model::Route::default();
|
||||||
let module = typst::model::eval(world.track(), route.track(), &world.source).unwrap();
|
let mut tracer = typst::model::Tracer::default();
|
||||||
|
let module = typst::model::eval(
|
||||||
|
world.track(),
|
||||||
|
route.track(),
|
||||||
|
tracer.track_mut(),
|
||||||
|
&world.source,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let content = module.content();
|
let content = module.content();
|
||||||
iai.run(|| typst::model::typeset(world.track(), &content));
|
iai.run(|| typst::model::typeset(world.track(), &content));
|
||||||
}
|
}
|
||||||
|
@ -435,7 +435,9 @@ fn test_part(
|
|||||||
if world.print.model {
|
if world.print.model {
|
||||||
let world = (world as &dyn World).track();
|
let world = (world as &dyn World).track();
|
||||||
let route = typst::model::Route::default();
|
let route = typst::model::Route::default();
|
||||||
let module = typst::model::eval(world, route.track(), source).unwrap();
|
let mut tracer = typst::model::Tracer::default();
|
||||||
|
let module =
|
||||||
|
typst::model::eval(world, route.track(), tracer.track_mut(), source).unwrap();
|
||||||
println!("Model:\n{:#?}\n", module.content());
|
println!("Model:\n{:#?}\n", module.content());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user