mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Refactor Scope
(#5797)
This commit is contained in:
parent
12dbb012b1
commit
eee903b0f8
@ -30,12 +30,14 @@ impl Access for ast::Ident<'_> {
|
|||||||
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||||
let span = self.span();
|
let span = self.span();
|
||||||
if vm.inspected == Some(span) {
|
if vm.inspected == Some(span) {
|
||||||
if let Ok(value) = vm.scopes.get(&self).cloned() {
|
if let Ok(binding) = vm.scopes.get(&self) {
|
||||||
vm.trace(value);
|
vm.trace(binding.read().clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let value = vm.scopes.get_mut(&self).at(span)?;
|
vm.scopes
|
||||||
Ok(value)
|
.get_mut(&self)
|
||||||
|
.and_then(|b| b.write().map_err(Into::into))
|
||||||
|
.at(span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ use typst_library::diag::{
|
|||||||
};
|
};
|
||||||
use typst_library::engine::{Engine, Sink, Traced};
|
use typst_library::engine::{Engine, Sink, Traced};
|
||||||
use typst_library::foundations::{
|
use typst_library::foundations::{
|
||||||
Arg, Args, Capturer, Closure, Content, Context, Func, NativeElement, Scope, Scopes,
|
Arg, Args, Binding, Capturer, Closure, Content, Context, Func, NativeElement, Scope,
|
||||||
SymbolElem, Value,
|
Scopes, SymbolElem, Value,
|
||||||
};
|
};
|
||||||
use typst_library::introspection::Introspector;
|
use typst_library::introspection::Introspector;
|
||||||
use typst_library::math::LrElem;
|
use typst_library::math::LrElem;
|
||||||
@ -196,7 +196,7 @@ pub fn eval_closure(
|
|||||||
|
|
||||||
// Provide the closure itself for recursive calls.
|
// Provide the closure itself for recursive calls.
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
vm.define(name, Value::Func(func.clone()));
|
vm.define(name, func.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let num_pos_args = args.to_pos().len();
|
let num_pos_args = args.to_pos().len();
|
||||||
@ -317,11 +317,11 @@ fn eval_field_call(
|
|||||||
|
|
||||||
if let Some(callee) = target.ty().scope().get(&field) {
|
if let Some(callee) = target.ty().scope().get(&field) {
|
||||||
args.insert(0, target_expr.span(), target);
|
args.insert(0, target_expr.span(), target);
|
||||||
Ok(FieldCall::Normal(callee.clone(), args))
|
Ok(FieldCall::Normal(callee.read().clone(), args))
|
||||||
} else if let Value::Content(content) = &target {
|
} else if let Value::Content(content) = &target {
|
||||||
if let Some(callee) = content.elem().scope().get(&field) {
|
if let Some(callee) = content.elem().scope().get(&field) {
|
||||||
args.insert(0, target_expr.span(), target);
|
args.insert(0, target_expr.span(), target);
|
||||||
Ok(FieldCall::Normal(callee.clone(), args))
|
Ok(FieldCall::Normal(callee.read().clone(), args))
|
||||||
} else {
|
} else {
|
||||||
bail!(missing_field_call_error(target, field))
|
bail!(missing_field_call_error(target, field))
|
||||||
}
|
}
|
||||||
@ -458,11 +458,9 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
// Identifiers that shouldn't count as captures because they
|
// Identifiers that shouldn't count as captures because they
|
||||||
// actually bind a new name are handled below (individually through
|
// actually bind a new name are handled below (individually through
|
||||||
// the expressions that contain them).
|
// the expressions that contain them).
|
||||||
Some(ast::Expr::Ident(ident)) => {
|
Some(ast::Expr::Ident(ident)) => self.capture(ident.get(), Scopes::get),
|
||||||
self.capture(ident.get(), ident.span(), Scopes::get)
|
|
||||||
}
|
|
||||||
Some(ast::Expr::MathIdent(ident)) => {
|
Some(ast::Expr::MathIdent(ident)) => {
|
||||||
self.capture(ident.get(), ident.span(), Scopes::get_in_math)
|
self.capture(ident.get(), Scopes::get_in_math)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code and content blocks create a scope.
|
// Code and content blocks create a scope.
|
||||||
@ -570,32 +568,34 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
|
|
||||||
/// Bind a new internal variable.
|
/// Bind a new internal variable.
|
||||||
fn bind(&mut self, ident: ast::Ident) {
|
fn bind(&mut self, ident: ast::Ident) {
|
||||||
self.internal.top.define_ident(ident, Value::None);
|
// The concrete value does not matter as we only use the scoping
|
||||||
|
// mechanism of `Scopes`, not the values themselves.
|
||||||
|
self.internal
|
||||||
|
.top
|
||||||
|
.bind(ident.get().clone(), Binding::detached(Value::None));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Capture a variable if it isn't internal.
|
/// Capture a variable if it isn't internal.
|
||||||
fn capture(
|
fn capture(
|
||||||
&mut self,
|
&mut self,
|
||||||
ident: &EcoString,
|
ident: &EcoString,
|
||||||
span: Span,
|
getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Binding>,
|
||||||
getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Value>,
|
|
||||||
) {
|
) {
|
||||||
if self.internal.get(ident).is_err() {
|
if self.internal.get(ident).is_ok() {
|
||||||
let Some(value) = self
|
return;
|
||||||
.external
|
|
||||||
.map(|external| getter(external, ident).ok())
|
|
||||||
.unwrap_or(Some(&Value::None))
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.captures.define_captured(
|
|
||||||
ident.clone(),
|
|
||||||
value.clone(),
|
|
||||||
self.capturer,
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let binding = match self.external {
|
||||||
|
Some(external) => match getter(external, ident) {
|
||||||
|
Ok(binding) => binding.capture(self.capturer),
|
||||||
|
Err(_) => return,
|
||||||
|
},
|
||||||
|
// The external scopes are only `None` when we are doing IDE capture
|
||||||
|
// analysis, in which case the concrete value doesn't matter.
|
||||||
|
None => Binding::detached(Value::None),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.captures.bind(ident.clone(), binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ impl Eval for ast::Ident<'_> {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
vm.scopes.get(&self).cloned().at(self.span())
|
Ok(vm.scopes.get(&self).at(self.span())?.read().clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ use typst_library::diag::{
|
|||||||
bail, error, warning, At, FileError, SourceResult, Trace, Tracepoint,
|
bail, error, warning, At, FileError, SourceResult, Trace, Tracepoint,
|
||||||
};
|
};
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Content, Module, Value};
|
use typst_library::foundations::{Binding, Content, Module, Value};
|
||||||
use typst_library::World;
|
use typst_library::World;
|
||||||
use typst_syntax::ast::{self, AstNode, BareImportError};
|
use typst_syntax::ast::{self, AstNode, BareImportError};
|
||||||
use typst_syntax::package::{PackageManifest, PackageSpec};
|
use typst_syntax::package::{PackageManifest, PackageSpec};
|
||||||
@ -43,7 +43,7 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source itself is imported if there is no import list or a rename.
|
// If there is a rename, import the source itself under that name.
|
||||||
let bare_name = self.bare_name();
|
let bare_name = self.bare_name();
|
||||||
let new_name = self.new_name();
|
let new_name = self.new_name();
|
||||||
if let Some(new_name) = new_name {
|
if let Some(new_name) = new_name {
|
||||||
@ -57,8 +57,7 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define renamed module on the scope.
|
vm.define(new_name, source.clone());
|
||||||
vm.scopes.top.define_ident(new_name, source.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = source.scope().unwrap();
|
let scope = source.scope().unwrap();
|
||||||
@ -76,7 +75,7 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
"this import has no effect",
|
"this import has no effect",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
vm.scopes.top.define_spanned(name, source, source_span);
|
vm.scopes.top.bind(name, Binding::new(source, source_span));
|
||||||
}
|
}
|
||||||
Ok(_) | Err(BareImportError::Dynamic) => bail!(
|
Ok(_) | Err(BareImportError::Dynamic) => bail!(
|
||||||
source_span, "dynamic import requires an explicit name";
|
source_span, "dynamic import requires an explicit name";
|
||||||
@ -92,8 +91,8 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ast::Imports::Wildcard) => {
|
Some(ast::Imports::Wildcard) => {
|
||||||
for (var, value, span) in scope.iter() {
|
for (var, binding) in scope.iter() {
|
||||||
vm.scopes.top.define_spanned(var.clone(), value.clone(), span);
|
vm.scopes.top.bind(var.clone(), binding.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ast::Imports::Items(items)) => {
|
Some(ast::Imports::Items(items)) => {
|
||||||
@ -103,7 +102,7 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
let mut scope = scope;
|
let mut scope = scope;
|
||||||
|
|
||||||
while let Some(component) = &path.next() {
|
while let Some(component) = &path.next() {
|
||||||
let Some(value) = scope.get(component) else {
|
let Some(binding) = scope.get(component) else {
|
||||||
errors.push(error!(component.span(), "unresolved import"));
|
errors.push(error!(component.span(), "unresolved import"));
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
@ -111,6 +110,7 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
if path.peek().is_some() {
|
if path.peek().is_some() {
|
||||||
// Nested import, as this is not the last component.
|
// Nested import, as this is not the last component.
|
||||||
// This must be a submodule.
|
// This must be a submodule.
|
||||||
|
let value = binding.read();
|
||||||
let Some(submodule) = value.scope() else {
|
let Some(submodule) = value.scope() else {
|
||||||
let error = if matches!(value, Value::Func(function) if function.scope().is_none())
|
let error = if matches!(value, Value::Func(function) if function.scope().is_none())
|
||||||
{
|
{
|
||||||
@ -153,7 +153,7 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.define(item.bound_name(), value.clone());
|
vm.bind(item.bound_name(), binding.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ impl Eval for ast::MathIdent<'_> {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
vm.scopes.get_in_math(&self).cloned().at(self.span())
|
Ok(vm.scopes.get_in_math(&self).at(self.span())?.read().clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use typst_library::diag::warning;
|
use typst_library::diag::warning;
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Context, IntoValue, Scopes, Value};
|
use typst_library::foundations::{Binding, Context, IntoValue, Scopes, Value};
|
||||||
use typst_library::World;
|
use typst_library::World;
|
||||||
use typst_syntax::ast::{self, AstNode};
|
use typst_syntax::ast::{self, AstNode};
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
@ -42,13 +42,23 @@ impl<'a> Vm<'a> {
|
|||||||
self.engine.world
|
self.engine.world
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a variable in the current scope.
|
/// Bind a value to an identifier.
|
||||||
|
///
|
||||||
|
/// This will create a [`Binding`] with the value and the identifier's span.
|
||||||
pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) {
|
pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) {
|
||||||
let value = value.into_value();
|
self.bind(var, Binding::new(value, var.span()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a binding into the current scope.
|
||||||
|
///
|
||||||
|
/// This will insert the value into the top-most scope and make it available
|
||||||
|
/// for dynamic tracing, assisting IDE functionality.
|
||||||
|
pub fn bind(&mut self, var: ast::Ident, binding: Binding) {
|
||||||
if self.inspected == Some(var.span()) {
|
if self.inspected == Some(var.span()) {
|
||||||
self.trace(value.clone());
|
self.trace(binding.read().clone());
|
||||||
}
|
}
|
||||||
// This will become an error in the parser if 'is' becomes a keyword.
|
|
||||||
|
// This will become an error in the parser if `is` becomes a keyword.
|
||||||
if var.get() == "is" {
|
if var.get() == "is" {
|
||||||
self.engine.sink.warn(warning!(
|
self.engine.sink.warn(warning!(
|
||||||
var.span(),
|
var.span(),
|
||||||
@ -58,7 +68,8 @@ impl<'a> Vm<'a> {
|
|||||||
hint: "try `is_` instead"
|
hint: "try `is_` instead"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
self.scopes.top.define_ident(var, value);
|
|
||||||
|
self.scopes.top.bind(var.get().clone(), binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trace a value.
|
/// Trace a value.
|
||||||
|
@ -398,13 +398,13 @@ fn field_access_completions(
|
|||||||
value: &Value,
|
value: &Value,
|
||||||
styles: &Option<Styles>,
|
styles: &Option<Styles>,
|
||||||
) {
|
) {
|
||||||
for (name, value, _) in value.ty().scope().iter() {
|
for (name, binding) in value.ty().scope().iter() {
|
||||||
ctx.call_completion(name.clone(), value);
|
ctx.call_completion(name.clone(), binding.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(scope) = value.scope() {
|
if let Some(scope) = value.scope() {
|
||||||
for (name, value, _) in scope.iter() {
|
for (name, binding) in scope.iter() {
|
||||||
ctx.call_completion(name.clone(), value);
|
ctx.call_completion(name.clone(), binding.read());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,9 +541,9 @@ fn import_item_completions<'a>(
|
|||||||
ctx.snippet_completion("*", "*", "Import everything.");
|
ctx.snippet_completion("*", "*", "Import everything.");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (name, value, _) in scope.iter() {
|
for (name, binding) in scope.iter() {
|
||||||
if existing.iter().all(|item| item.original_name().as_str() != name) {
|
if existing.iter().all(|item| item.original_name().as_str() != name) {
|
||||||
ctx.value_completion(name.clone(), value);
|
ctx.value_completion(name.clone(), binding.read());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -846,13 +846,11 @@ fn resolve_global_callee<'a>(
|
|||||||
) -> Option<&'a Func> {
|
) -> Option<&'a Func> {
|
||||||
let globals = globals(ctx.world, ctx.leaf);
|
let globals = globals(ctx.world, ctx.leaf);
|
||||||
let value = match callee {
|
let value = match callee {
|
||||||
ast::Expr::Ident(ident) => globals.get(&ident)?,
|
ast::Expr::Ident(ident) => globals.get(&ident)?.read(),
|
||||||
ast::Expr::FieldAccess(access) => match access.target() {
|
ast::Expr::FieldAccess(access) => match access.target() {
|
||||||
ast::Expr::Ident(target) => match globals.get(&target)? {
|
ast::Expr::Ident(target) => {
|
||||||
Value::Module(module) => module.field(&access.field()).ok()?,
|
globals.get(&target)?.read().scope()?.get(&access.field())?.read()
|
||||||
Value::Func(func) => func.field(&access.field()).ok()?,
|
}
|
||||||
_ => return None,
|
|
||||||
},
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
_ => return None,
|
_ => return None,
|
||||||
@ -1464,7 +1462,8 @@ impl<'a> CompletionContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (name, value, _) in globals(self.world, self.leaf).iter() {
|
for (name, binding) in globals(self.world, self.leaf).iter() {
|
||||||
|
let value = binding.read();
|
||||||
if filter(value) && !defined.contains_key(name) {
|
if filter(value) && !defined.contains_key(name) {
|
||||||
self.value_completion_full(Some(name.clone()), value, parens, None, None);
|
self.value_completion_full(Some(name.clone()), value, parens, None, None);
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,8 @@ pub fn definition(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = globals(world, &leaf).get(&name) {
|
if let Some(binding) = globals(world, &leaf).get(&name) {
|
||||||
return Some(Definition::Std(value.clone()));
|
return Some(Definition::Std(binding.read().clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,8 +76,12 @@ pub fn named_items<T>(
|
|||||||
// ```
|
// ```
|
||||||
Some(ast::Imports::Wildcard) => {
|
Some(ast::Imports::Wildcard) => {
|
||||||
if let Some(scope) = source_value.and_then(Value::scope) {
|
if let Some(scope) = source_value.and_then(Value::scope) {
|
||||||
for (name, value, span) in scope.iter() {
|
for (name, binding) in scope.iter() {
|
||||||
let item = NamedItem::Import(name, span, Some(value));
|
let item = NamedItem::Import(
|
||||||
|
name,
|
||||||
|
binding.span(),
|
||||||
|
Some(binding.read()),
|
||||||
|
);
|
||||||
if let Some(res) = recv(item) {
|
if let Some(res) = recv(item) {
|
||||||
return Some(res);
|
return Some(res);
|
||||||
}
|
}
|
||||||
@ -89,24 +93,26 @@ pub fn named_items<T>(
|
|||||||
// ```
|
// ```
|
||||||
Some(ast::Imports::Items(items)) => {
|
Some(ast::Imports::Items(items)) => {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
|
let mut iter = item.path().iter();
|
||||||
|
let mut binding = source_value
|
||||||
|
.and_then(Value::scope)
|
||||||
|
.zip(iter.next())
|
||||||
|
.and_then(|(scope, first)| scope.get(&first));
|
||||||
|
|
||||||
|
for ident in iter {
|
||||||
|
binding = binding.and_then(|binding| {
|
||||||
|
binding.read().scope()?.get(&ident)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let bound = item.bound_name();
|
let bound = item.bound_name();
|
||||||
|
let (span, value) = match binding {
|
||||||
|
Some(binding) => (binding.span(), Some(binding.read())),
|
||||||
|
None => (bound.span(), None),
|
||||||
|
};
|
||||||
|
|
||||||
let (span, value) = item.path().iter().fold(
|
let item = NamedItem::Import(bound.get(), span, value);
|
||||||
(bound.span(), source_value),
|
if let Some(res) = recv(item) {
|
||||||
|(span, value), path_ident| {
|
|
||||||
let scope = value.and_then(|v| v.scope());
|
|
||||||
let span = scope
|
|
||||||
.and_then(|s| s.get_span(&path_ident))
|
|
||||||
.unwrap_or(Span::detached())
|
|
||||||
.or(span);
|
|
||||||
let value = scope.and_then(|s| s.get(&path_ident));
|
|
||||||
(span, value)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(res) =
|
|
||||||
recv(NamedItem::Import(bound.get(), span, value))
|
|
||||||
{
|
|
||||||
return Some(res);
|
return Some(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::fmt::Write;
|
|||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use typst::engine::Sink;
|
use typst::engine::Sink;
|
||||||
use typst::foundations::{repr, Capturer, CastInfo, Repr, Value};
|
use typst::foundations::{repr, Binding, Capturer, CastInfo, Repr, Value};
|
||||||
use typst::layout::{Length, PagedDocument};
|
use typst::layout::{Length, PagedDocument};
|
||||||
use typst::syntax::ast::AstNode;
|
use typst::syntax::ast::AstNode;
|
||||||
use typst::syntax::{ast, LinkedNode, Side, Source, SyntaxKind};
|
use typst::syntax::{ast, LinkedNode, Side, Source, SyntaxKind};
|
||||||
@ -206,7 +206,12 @@ fn named_param_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option<Toolti
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Find metadata about the function.
|
// Find metadata about the function.
|
||||||
if let Some(Value::Func(func)) = world.library().global.scope().get(&callee);
|
if let Some(Value::Func(func)) = world
|
||||||
|
.library()
|
||||||
|
.global
|
||||||
|
.scope()
|
||||||
|
.get(&callee)
|
||||||
|
.map(Binding::read);
|
||||||
then { (func, named) }
|
then { (func, named) }
|
||||||
else { return None; }
|
else { return None; }
|
||||||
};
|
};
|
||||||
@ -352,6 +357,13 @@ mod tests {
|
|||||||
.must_be_text("This closure captures `f` and `y`");
|
.must_be_text("This closure captures `f` and `y`");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tooltip_import() {
|
||||||
|
let world = TestWorld::new("#import \"other.typ\": a, b")
|
||||||
|
.with_source("other.typ", "#let (a, b, c) = (1, 2, 3)");
|
||||||
|
test(&world, -5, Side::After).must_be_code("1");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tooltip_star_import() {
|
fn test_tooltip_star_import() {
|
||||||
let world = TestWorld::new("#import \"other.typ\": *")
|
let world = TestWorld::new("#import \"other.typ\": *")
|
||||||
|
@ -171,7 +171,7 @@ where
|
|||||||
self.find_iter(content.fields().iter().map(|(_, v)| v))?;
|
self.find_iter(content.fields().iter().map(|(_, v)| v))?;
|
||||||
}
|
}
|
||||||
Value::Module(module) => {
|
Value::Module(module) => {
|
||||||
self.find_iter(module.scope().iter().map(|(_, v, _)| v))?;
|
self.find_iter(module.scope().iter().map(|(_, b)| b.read()))?;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -261,7 +261,12 @@ pub struct ToDict(Dict);
|
|||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
ToDict,
|
ToDict,
|
||||||
v: Module => Self(v.scope().iter().map(|(k, v, _)| (Str::from(k.clone()), v.clone())).collect()),
|
v: Module => Self(v
|
||||||
|
.scope()
|
||||||
|
.iter()
|
||||||
|
.map(|(k, b)| (Str::from(k.clone()), b.read().clone()))
|
||||||
|
.collect()
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Dict {
|
impl Debug for Dict {
|
||||||
|
@ -259,7 +259,7 @@ impl Func {
|
|||||||
let scope =
|
let scope =
|
||||||
self.scope().ok_or("cannot access fields on user-defined functions")?;
|
self.scope().ok_or("cannot access fields on user-defined functions")?;
|
||||||
match scope.get(field) {
|
match scope.get(field) {
|
||||||
Some(field) => Ok(field),
|
Some(binding) => Ok(binding.read()),
|
||||||
None => match self.name() {
|
None => match self.name() {
|
||||||
Some(name) => bail!("function `{name}` does not contain field `{field}`"),
|
Some(name) => bail!("function `{name}` does not contain field `{field}`"),
|
||||||
None => bail!("function does not contain field `{field}`"),
|
None => bail!("function does not contain field `{field}`"),
|
||||||
|
@ -94,7 +94,7 @@ pub static FOUNDATIONS: Category;
|
|||||||
|
|
||||||
/// Hook up all `foundations` definitions.
|
/// Hook up all `foundations` definitions.
|
||||||
pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) {
|
pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) {
|
||||||
global.category(FOUNDATIONS);
|
global.start_category(FOUNDATIONS);
|
||||||
global.define_type::<bool>();
|
global.define_type::<bool>();
|
||||||
global.define_type::<i64>();
|
global.define_type::<i64>();
|
||||||
global.define_type::<f64>();
|
global.define_type::<f64>();
|
||||||
@ -301,7 +301,7 @@ pub fn eval(
|
|||||||
let dict = scope;
|
let dict = scope;
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
for (key, value) in dict {
|
for (key, value) in dict {
|
||||||
scope.define_spanned(key, value, span);
|
scope.bind(key.into(), Binding::new(value, span));
|
||||||
}
|
}
|
||||||
(engine.routines.eval_string)(engine.routines, engine.world, &text, span, mode, scope)
|
(engine.routines.eval_string)(engine.routines, engine.world, &text, span, mode, scope)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use typst_syntax::FileId;
|
use typst_syntax::FileId;
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::foundations::{repr, ty, Content, Scope, Value};
|
use crate::foundations::{repr, ty, Content, Scope, Value};
|
||||||
|
|
||||||
/// An module of definitions.
|
/// An module of definitions.
|
||||||
@ -118,11 +118,14 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Try to access a definition in the module.
|
/// Try to access a definition in the module.
|
||||||
pub fn field(&self, name: &str) -> StrResult<&Value> {
|
pub fn field(&self, field: &str) -> StrResult<&Value> {
|
||||||
self.scope().get(name).ok_or_else(|| match &self.name {
|
match self.scope().get(field) {
|
||||||
Some(module) => eco_format!("module `{module}` does not contain `{name}`"),
|
Some(binding) => Ok(binding.read()),
|
||||||
None => eco_format!("module does not contain `{name}`"),
|
None => match &self.name {
|
||||||
})
|
Some(name) => bail!("module `{name}` does not contain `{field}`"),
|
||||||
|
None => bail!("module does not contain `{field}`"),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the module's content.
|
/// Extract the module's content.
|
||||||
|
@ -8,7 +8,7 @@ use wasmi::Memory;
|
|||||||
|
|
||||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{cast, func, scope, Bytes, Func, Module, Scope, Value};
|
use crate::foundations::{cast, func, scope, Binding, Bytes, Func, Module, Scope, Value};
|
||||||
use crate::loading::{DataSource, Load};
|
use crate::loading::{DataSource, Load};
|
||||||
|
|
||||||
/// Loads a WebAssembly module.
|
/// Loads a WebAssembly module.
|
||||||
@ -369,7 +369,7 @@ impl Plugin {
|
|||||||
if matches!(export.ty(), wasmi::ExternType::Func(_)) {
|
if matches!(export.ty(), wasmi::ExternType::Func(_)) {
|
||||||
let name = EcoString::from(export.name());
|
let name = EcoString::from(export.name());
|
||||||
let func = PluginFunc { plugin: shared.clone(), name: name.clone() };
|
let func = PluginFunc { plugin: shared.clone(), name: name.clone() };
|
||||||
scope.define(name, Func::from(func));
|
scope.bind(name, Binding::detached(Func::from(func)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
|
use indexmap::map::Entry;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use typst_syntax::ast::{self, AstNode};
|
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
use typst_utils::Static;
|
use typst_utils::Static;
|
||||||
|
|
||||||
@ -46,14 +46,14 @@ impl<'a> Scopes<'a> {
|
|||||||
self.top = self.scopes.pop().expect("no pushed scope");
|
self.top = self.scopes.pop().expect("no pushed scope");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to access a variable immutably.
|
/// Try to access a binding immutably.
|
||||||
pub fn get(&self, var: &str) -> HintedStrResult<&Value> {
|
pub fn get(&self, var: &str) -> HintedStrResult<&Binding> {
|
||||||
std::iter::once(&self.top)
|
std::iter::once(&self.top)
|
||||||
.chain(self.scopes.iter().rev())
|
.chain(self.scopes.iter().rev())
|
||||||
.find_map(|scope| scope.get(var))
|
.find_map(|scope| scope.get(var))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.base.and_then(|base| match base.global.scope().get(var) {
|
self.base.and_then(|base| match base.global.scope().get(var) {
|
||||||
Some(value) => Some(value),
|
Some(binding) => Some(binding),
|
||||||
None if var == "std" => Some(&base.std),
|
None if var == "std" => Some(&base.std),
|
||||||
None => None,
|
None => None,
|
||||||
})
|
})
|
||||||
@ -61,14 +61,28 @@ impl<'a> Scopes<'a> {
|
|||||||
.ok_or_else(|| unknown_variable(var))
|
.ok_or_else(|| unknown_variable(var))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to access a variable immutably in math.
|
/// Try to access a binding mutably.
|
||||||
pub fn get_in_math(&self, var: &str) -> HintedStrResult<&Value> {
|
pub fn get_mut(&mut self, var: &str) -> HintedStrResult<&mut Binding> {
|
||||||
|
std::iter::once(&mut self.top)
|
||||||
|
.chain(&mut self.scopes.iter_mut().rev())
|
||||||
|
.find_map(|scope| scope.get_mut(var))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
match self.base.and_then(|base| base.global.scope().get(var)) {
|
||||||
|
Some(_) => cannot_mutate_constant(var),
|
||||||
|
_ if var == "std" => cannot_mutate_constant(var),
|
||||||
|
_ => unknown_variable(var),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to access a binding immutably in math.
|
||||||
|
pub fn get_in_math(&self, var: &str) -> HintedStrResult<&Binding> {
|
||||||
std::iter::once(&self.top)
|
std::iter::once(&self.top)
|
||||||
.chain(self.scopes.iter().rev())
|
.chain(self.scopes.iter().rev())
|
||||||
.find_map(|scope| scope.get(var))
|
.find_map(|scope| scope.get(var))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.base.and_then(|base| match base.math.scope().get(var) {
|
self.base.and_then(|base| match base.math.scope().get(var) {
|
||||||
Some(value) => Some(value),
|
Some(binding) => Some(binding),
|
||||||
None if var == "std" => Some(&base.std),
|
None if var == "std" => Some(&base.std),
|
||||||
None => None,
|
None => None,
|
||||||
})
|
})
|
||||||
@ -81,20 +95,6 @@ impl<'a> Scopes<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to access a variable mutably.
|
|
||||||
pub fn get_mut(&mut self, var: &str) -> HintedStrResult<&mut Value> {
|
|
||||||
std::iter::once(&mut self.top)
|
|
||||||
.chain(&mut self.scopes.iter_mut().rev())
|
|
||||||
.find_map(|scope| scope.get_mut(var))
|
|
||||||
.ok_or_else(|| {
|
|
||||||
match self.base.and_then(|base| base.global.scope().get(var)) {
|
|
||||||
Some(_) => cannot_mutate_constant(var),
|
|
||||||
_ if var == "std" => cannot_mutate_constant(var),
|
|
||||||
_ => unknown_variable(var),
|
|
||||||
}
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if an std variable is shadowed.
|
/// Check if an std variable is shadowed.
|
||||||
pub fn check_std_shadowed(&self, var: &str) -> bool {
|
pub fn check_std_shadowed(&self, var: &str) -> bool {
|
||||||
self.base.is_some_and(|base| base.global.scope().get(var).is_some())
|
self.base.is_some_and(|base| base.global.scope().get(var).is_some())
|
||||||
@ -104,84 +104,28 @@ impl<'a> Scopes<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn cannot_mutate_constant(var: &str) -> HintedString {
|
|
||||||
eco_format!("cannot mutate a constant: {}", var).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error message when a variable is not found.
|
|
||||||
#[cold]
|
|
||||||
fn unknown_variable(var: &str) -> HintedString {
|
|
||||||
let mut res = HintedString::new(eco_format!("unknown variable: {}", var));
|
|
||||||
|
|
||||||
if var.contains('-') {
|
|
||||||
res.hint(eco_format!(
|
|
||||||
"if you meant to use subtraction, try adding spaces around the minus sign{}: `{}`",
|
|
||||||
if var.matches('-').count() > 1 { "s" } else { "" },
|
|
||||||
var.replace('-', " - ")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn unknown_variable_math(var: &str, in_global: bool) -> HintedString {
|
|
||||||
let mut res = HintedString::new(eco_format!("unknown variable: {}", var));
|
|
||||||
|
|
||||||
if matches!(var, "none" | "auto" | "false" | "true") {
|
|
||||||
res.hint(eco_format!(
|
|
||||||
"if you meant to use a literal, try adding a hash before it: `#{var}`",
|
|
||||||
));
|
|
||||||
} else if in_global {
|
|
||||||
res.hint(eco_format!(
|
|
||||||
"`{var}` is not available directly in math, try adding a hash before it: `#{var}`",
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
res.hint(eco_format!(
|
|
||||||
"if you meant to display multiple letters as is, try adding spaces between each letter: `{}`",
|
|
||||||
var.chars()
|
|
||||||
.flat_map(|c| [' ', c])
|
|
||||||
.skip(1)
|
|
||||||
.collect::<EcoString>()
|
|
||||||
));
|
|
||||||
res.hint(eco_format!(
|
|
||||||
"or if you meant to display this as text, try placing it in quotes: `\"{var}\"`"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A map from binding names to values.
|
/// A map from binding names to values.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
map: IndexMap<EcoString, Slot>,
|
map: IndexMap<EcoString, Binding>,
|
||||||
deduplicate: bool,
|
deduplicate: bool,
|
||||||
category: Option<Category>,
|
category: Option<Category>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scope construction.
|
||||||
impl Scope {
|
impl Scope {
|
||||||
/// Create a new empty scope.
|
/// Create a new empty scope.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new scope with the given capacity.
|
|
||||||
pub fn with_capacity(capacity: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
map: IndexMap::with_capacity(capacity),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new scope with duplication prevention.
|
/// Create a new scope with duplication prevention.
|
||||||
pub fn deduplicating() -> Self {
|
pub fn deduplicating() -> Self {
|
||||||
Self { deduplicate: true, ..Default::default() }
|
Self { deduplicate: true, ..Default::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enter a new category.
|
/// Enter a new category.
|
||||||
pub fn category(&mut self, category: Category) {
|
pub fn start_category(&mut self, category: Category) {
|
||||||
self.category = Some(category);
|
self.category = Some(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,102 +134,87 @@ impl Scope {
|
|||||||
self.category = None;
|
self.category = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bind a value to a name.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn define(&mut self, name: impl Into<EcoString>, value: impl IntoValue) {
|
|
||||||
self.define_spanned(name, value, Span::detached())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bind a value to a name defined by an identifier.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn define_ident(&mut self, ident: ast::Ident, value: impl IntoValue) {
|
|
||||||
self.define_spanned(ident.get().clone(), value, ident.span())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bind a value to a name.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn define_spanned(
|
|
||||||
&mut self,
|
|
||||||
name: impl Into<EcoString>,
|
|
||||||
value: impl IntoValue,
|
|
||||||
span: Span,
|
|
||||||
) {
|
|
||||||
let name = name.into();
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
if self.deduplicate && self.map.contains_key(&name) {
|
|
||||||
panic!("duplicate definition: {name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.map.insert(
|
|
||||||
name,
|
|
||||||
Slot::new(value.into_value(), span, Kind::Normal, self.category),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a captured, immutable binding.
|
|
||||||
pub fn define_captured(
|
|
||||||
&mut self,
|
|
||||||
name: EcoString,
|
|
||||||
value: Value,
|
|
||||||
capturer: Capturer,
|
|
||||||
span: Span,
|
|
||||||
) {
|
|
||||||
self.map.insert(
|
|
||||||
name,
|
|
||||||
Slot::new(value.into_value(), span, Kind::Captured(capturer), self.category),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a native function through a Rust type that shadows the function.
|
/// Define a native function through a Rust type that shadows the function.
|
||||||
pub fn define_func<T: NativeFunc>(&mut self) {
|
#[track_caller]
|
||||||
|
pub fn define_func<T: NativeFunc>(&mut self) -> &mut Binding {
|
||||||
let data = T::data();
|
let data = T::data();
|
||||||
self.define(data.name, Func::from(data));
|
self.define(data.name, Func::from(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a native function with raw function data.
|
/// Define a native function with raw function data.
|
||||||
pub fn define_func_with_data(&mut self, data: &'static NativeFuncData) {
|
#[track_caller]
|
||||||
self.define(data.name, Func::from(data));
|
pub fn define_func_with_data(
|
||||||
|
&mut self,
|
||||||
|
data: &'static NativeFuncData,
|
||||||
|
) -> &mut Binding {
|
||||||
|
self.define(data.name, Func::from(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a native type.
|
/// Define a native type.
|
||||||
pub fn define_type<T: NativeType>(&mut self) {
|
#[track_caller]
|
||||||
|
pub fn define_type<T: NativeType>(&mut self) -> &mut Binding {
|
||||||
let data = T::data();
|
let data = T::data();
|
||||||
self.define(data.name, Type::from(data));
|
self.define(data.name, Type::from(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a native element.
|
/// Define a native element.
|
||||||
pub fn define_elem<T: NativeElement>(&mut self) {
|
#[track_caller]
|
||||||
|
pub fn define_elem<T: NativeElement>(&mut self) -> &mut Binding {
|
||||||
let data = T::data();
|
let data = T::data();
|
||||||
self.define(data.name, Element::from(data));
|
self.define(data.name, Element::from(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to access a variable immutably.
|
/// Define a built-in with compile-time known name and returns a mutable
|
||||||
pub fn get(&self, var: &str) -> Option<&Value> {
|
/// reference to it.
|
||||||
self.map.get(var).map(Slot::read)
|
///
|
||||||
|
/// When the name isn't compile-time known, you should instead use:
|
||||||
|
/// - `Vm::bind` if you already have [`Binding`]
|
||||||
|
/// - `Vm::define` if you only have a [`Value`]
|
||||||
|
/// - [`Scope::bind`](Self::bind) if you are not operating in the context of
|
||||||
|
/// a `Vm` or if you are binding to something that is not an AST
|
||||||
|
/// identifier (e.g. when constructing a dynamic
|
||||||
|
/// [`Module`](super::Module))
|
||||||
|
#[track_caller]
|
||||||
|
pub fn define(&mut self, name: &'static str, value: impl IntoValue) -> &mut Binding {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if self.deduplicate && self.map.contains_key(name) {
|
||||||
|
panic!("duplicate definition: {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut binding = Binding::detached(value);
|
||||||
|
binding.category = self.category;
|
||||||
|
self.bind(name.into(), binding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scope manipulation and access.
|
||||||
|
impl Scope {
|
||||||
|
/// Inserts a binding into this scope and returns a mutable reference to it.
|
||||||
|
///
|
||||||
|
/// Prefer `Vm::bind` if you are operating in the context of a `Vm`.
|
||||||
|
pub fn bind(&mut self, name: EcoString, binding: Binding) -> &mut Binding {
|
||||||
|
match self.map.entry(name) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
entry.insert(binding);
|
||||||
|
entry.into_mut()
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => entry.insert(binding),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to access a variable mutably.
|
/// Try to access a binding immutably.
|
||||||
pub fn get_mut(&mut self, var: &str) -> Option<HintedStrResult<&mut Value>> {
|
pub fn get(&self, var: &str) -> Option<&Binding> {
|
||||||
self.map
|
self.map.get(var)
|
||||||
.get_mut(var)
|
|
||||||
.map(Slot::write)
|
|
||||||
.map(|res| res.map_err(HintedString::from))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the span of a definition.
|
/// Try to access a binding mutably.
|
||||||
pub fn get_span(&self, var: &str) -> Option<Span> {
|
pub fn get_mut(&mut self, var: &str) -> Option<&mut Binding> {
|
||||||
Some(self.map.get(var)?.span)
|
self.map.get_mut(var)
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the category of a definition.
|
|
||||||
pub fn get_category(&self, var: &str) -> Option<Category> {
|
|
||||||
self.map.get(var)?.category
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all definitions.
|
/// Iterate over all definitions.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Value, Span)> {
|
pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Binding)> {
|
||||||
self.map.iter().map(|(k, v)| (k, v.read(), v.span))
|
self.map.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,28 +247,85 @@ pub trait NativeScope {
|
|||||||
fn scope() -> Scope;
|
fn scope() -> Scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A slot where a value is stored.
|
/// A bound value with metadata.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
struct Slot {
|
pub struct Binding {
|
||||||
/// The stored value.
|
/// The bound value.
|
||||||
value: Value,
|
value: Value,
|
||||||
/// The kind of slot, determines how the value can be accessed.
|
/// The kind of binding, determines how the value can be accessed.
|
||||||
kind: Kind,
|
kind: BindingKind,
|
||||||
/// A span associated with the stored value.
|
/// A span associated with the binding.
|
||||||
span: Span,
|
span: Span,
|
||||||
/// The category of the slot.
|
/// The category of the binding.
|
||||||
category: Option<Category>,
|
category: Option<Category>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The different kinds of slots.
|
/// The different kinds of slots.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
enum Kind {
|
enum BindingKind {
|
||||||
/// A normal, mutable binding.
|
/// A normal, mutable binding.
|
||||||
Normal,
|
Normal,
|
||||||
/// A captured copy of another variable.
|
/// A captured copy of another variable.
|
||||||
Captured(Capturer),
|
Captured(Capturer),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Binding {
|
||||||
|
/// Create a new binding with a span marking its definition site.
|
||||||
|
pub fn new(value: impl IntoValue, span: Span) -> Self {
|
||||||
|
Self {
|
||||||
|
value: value.into_value(),
|
||||||
|
span,
|
||||||
|
kind: BindingKind::Normal,
|
||||||
|
category: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a binding without a span.
|
||||||
|
pub fn detached(value: impl IntoValue) -> Self {
|
||||||
|
Self::new(value, Span::detached())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the value.
|
||||||
|
pub fn read(&self) -> &Value {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to write to the value.
|
||||||
|
///
|
||||||
|
/// This fails if the value is a read-only closure capture.
|
||||||
|
pub fn write(&mut self) -> StrResult<&mut Value> {
|
||||||
|
match self.kind {
|
||||||
|
BindingKind::Normal => Ok(&mut self.value),
|
||||||
|
BindingKind::Captured(capturer) => bail!(
|
||||||
|
"variables from outside the {} are \
|
||||||
|
read-only and cannot be modified",
|
||||||
|
match capturer {
|
||||||
|
Capturer::Function => "function",
|
||||||
|
Capturer::Context => "context expression",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of the binding for closure capturing.
|
||||||
|
pub fn capture(&self, capturer: Capturer) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: BindingKind::Captured(capturer),
|
||||||
|
..self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A span associated with the stored value.
|
||||||
|
pub fn span(&self) -> Span {
|
||||||
|
self.span
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The category of the value, if any.
|
||||||
|
pub fn category(&self) -> Option<Category> {
|
||||||
|
self.category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// What the variable was captured by.
|
/// What the variable was captured by.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Capturer {
|
pub enum Capturer {
|
||||||
@ -349,35 +335,6 @@ pub enum Capturer {
|
|||||||
Context,
|
Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Slot {
|
|
||||||
/// Create a new slot.
|
|
||||||
fn new(value: Value, span: Span, kind: Kind, category: Option<Category>) -> Self {
|
|
||||||
Self { value, span, kind, category }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the value.
|
|
||||||
fn read(&self) -> &Value {
|
|
||||||
&self.value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to write to the value.
|
|
||||||
fn write(&mut self) -> StrResult<&mut Value> {
|
|
||||||
match self.kind {
|
|
||||||
Kind::Normal => Ok(&mut self.value),
|
|
||||||
Kind::Captured(capturer) => {
|
|
||||||
bail!(
|
|
||||||
"variables from outside the {} are \
|
|
||||||
read-only and cannot be modified",
|
|
||||||
match capturer {
|
|
||||||
Capturer::Function => "function",
|
|
||||||
Capturer::Context => "context expression",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A group of related definitions.
|
/// A group of related definitions.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Category(Static<CategoryData>);
|
pub struct Category(Static<CategoryData>);
|
||||||
@ -417,3 +374,57 @@ pub struct CategoryData {
|
|||||||
pub title: &'static str,
|
pub title: &'static str,
|
||||||
pub docs: &'static str,
|
pub docs: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The error message when trying to mutate a variable from the standard
|
||||||
|
/// library.
|
||||||
|
#[cold]
|
||||||
|
fn cannot_mutate_constant(var: &str) -> HintedString {
|
||||||
|
eco_format!("cannot mutate a constant: {}", var).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error message when a variable wasn't found.
|
||||||
|
#[cold]
|
||||||
|
fn unknown_variable(var: &str) -> HintedString {
|
||||||
|
let mut res = HintedString::new(eco_format!("unknown variable: {}", var));
|
||||||
|
|
||||||
|
if var.contains('-') {
|
||||||
|
res.hint(eco_format!(
|
||||||
|
"if you meant to use subtraction, \
|
||||||
|
try adding spaces around the minus sign{}: `{}`",
|
||||||
|
if var.matches('-').count() > 1 { "s" } else { "" },
|
||||||
|
var.replace('-', " - ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error message when a variable wasn't found it math.
|
||||||
|
#[cold]
|
||||||
|
fn unknown_variable_math(var: &str, in_global: bool) -> HintedString {
|
||||||
|
let mut res = HintedString::new(eco_format!("unknown variable: {}", var));
|
||||||
|
|
||||||
|
if matches!(var, "none" | "auto" | "false" | "true") {
|
||||||
|
res.hint(eco_format!(
|
||||||
|
"if you meant to use a literal, \
|
||||||
|
try adding a hash before it: `#{var}`",
|
||||||
|
));
|
||||||
|
} else if in_global {
|
||||||
|
res.hint(eco_format!(
|
||||||
|
"`{var}` is not available directly in math, \
|
||||||
|
try adding a hash before it: `#{var}`",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
res.hint(eco_format!(
|
||||||
|
"if you meant to display multiple letters as is, \
|
||||||
|
try adding spaces between each letter: `{}`",
|
||||||
|
var.chars().flat_map(|c| [' ', c]).skip(1).collect::<EcoString>()
|
||||||
|
));
|
||||||
|
res.hint(eco_format!(
|
||||||
|
"or if you meant to display this as text, \
|
||||||
|
try placing it in quotes: `\"{var}\"`"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ use std::sync::LazyLock;
|
|||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use typst_utils::Static;
|
use typst_utils::Static;
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, func, AutoValue, Func, NativeFuncData, NoneValue, Repr, Scope, Value,
|
cast, func, AutoValue, Func, NativeFuncData, NoneValue, Repr, Scope, Value,
|
||||||
};
|
};
|
||||||
@ -95,9 +95,10 @@ impl Type {
|
|||||||
|
|
||||||
/// Get a field from this type's scope, if possible.
|
/// Get a field from this type's scope, if possible.
|
||||||
pub fn field(&self, field: &str) -> StrResult<&'static Value> {
|
pub fn field(&self, field: &str) -> StrResult<&'static Value> {
|
||||||
self.scope()
|
match self.scope().get(field) {
|
||||||
.get(field)
|
Some(binding) => Ok(binding.read()),
|
||||||
.ok_or_else(|| eco_format!("type {self} does not contain field `{field}`"))
|
None => bail!("type {self} does not contain field `{field}`"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ pub static HTML: Category;
|
|||||||
/// Create a module with all HTML definitions.
|
/// Create a module with all HTML definitions.
|
||||||
pub fn module() -> Module {
|
pub fn module() -> Module {
|
||||||
let mut html = Scope::deduplicating();
|
let mut html = Scope::deduplicating();
|
||||||
html.category(HTML);
|
html.start_category(HTML);
|
||||||
html.define_elem::<HtmlElem>();
|
html.define_elem::<HtmlElem>();
|
||||||
html.define_elem::<FrameElem>();
|
html.define_elem::<FrameElem>();
|
||||||
Module::new("html", html)
|
Module::new("html", html)
|
||||||
|
@ -42,7 +42,7 @@ pub static INTROSPECTION: Category;
|
|||||||
|
|
||||||
/// Hook up all `introspection` definitions.
|
/// Hook up all `introspection` definitions.
|
||||||
pub fn define(global: &mut Scope) {
|
pub fn define(global: &mut Scope) {
|
||||||
global.category(INTROSPECTION);
|
global.start_category(INTROSPECTION);
|
||||||
global.define_type::<Location>();
|
global.define_type::<Location>();
|
||||||
global.define_type::<Counter>();
|
global.define_type::<Counter>();
|
||||||
global.define_type::<State>();
|
global.define_type::<State>();
|
||||||
|
@ -74,7 +74,7 @@ pub static LAYOUT: Category;
|
|||||||
|
|
||||||
/// Hook up all `layout` definitions.
|
/// Hook up all `layout` definitions.
|
||||||
pub fn define(global: &mut Scope) {
|
pub fn define(global: &mut Scope) {
|
||||||
global.category(LAYOUT);
|
global.start_category(LAYOUT);
|
||||||
global.define_type::<Length>();
|
global.define_type::<Length>();
|
||||||
global.define_type::<Angle>();
|
global.define_type::<Angle>();
|
||||||
global.define_type::<Ratio>();
|
global.define_type::<Ratio>();
|
||||||
|
@ -33,7 +33,7 @@ use typst_syntax::{FileId, Source, Span};
|
|||||||
use typst_utils::{LazyHash, SmallBitSet};
|
use typst_utils::{LazyHash, SmallBitSet};
|
||||||
|
|
||||||
use crate::diag::FileResult;
|
use crate::diag::FileResult;
|
||||||
use crate::foundations::{Array, Bytes, Datetime, Dict, Module, Scope, Styles, Value};
|
use crate::foundations::{Array, Binding, Bytes, Datetime, Dict, Module, Scope, Styles};
|
||||||
use crate::layout::{Alignment, Dir};
|
use crate::layout::{Alignment, Dir};
|
||||||
use crate::text::{Font, FontBook};
|
use crate::text::{Font, FontBook};
|
||||||
use crate::visualize::Color;
|
use crate::visualize::Color;
|
||||||
@ -148,7 +148,7 @@ pub struct Library {
|
|||||||
/// everything else configurable via set and show rules).
|
/// everything else configurable via set and show rules).
|
||||||
pub styles: Styles,
|
pub styles: Styles,
|
||||||
/// The standard library as a value. Used to provide the `std` variable.
|
/// The standard library as a value. Used to provide the `std` variable.
|
||||||
pub std: Value,
|
pub std: Binding,
|
||||||
/// In-development features that were enabled.
|
/// In-development features that were enabled.
|
||||||
pub features: Features,
|
pub features: Features,
|
||||||
}
|
}
|
||||||
@ -196,12 +196,11 @@ impl LibraryBuilder {
|
|||||||
let math = math::module();
|
let math = math::module();
|
||||||
let inputs = self.inputs.unwrap_or_default();
|
let inputs = self.inputs.unwrap_or_default();
|
||||||
let global = global(math.clone(), inputs, &self.features);
|
let global = global(math.clone(), inputs, &self.features);
|
||||||
let std = Value::Module(global.clone());
|
|
||||||
Library {
|
Library {
|
||||||
global,
|
global: global.clone(),
|
||||||
math,
|
math,
|
||||||
styles: Styles::new(),
|
styles: Styles::new(),
|
||||||
std,
|
std: Binding::detached(global),
|
||||||
features: self.features,
|
features: self.features,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ pub static DATA_LOADING: Category;
|
|||||||
|
|
||||||
/// Hook up all `data-loading` definitions.
|
/// Hook up all `data-loading` definitions.
|
||||||
pub(super) fn define(global: &mut Scope) {
|
pub(super) fn define(global: &mut Scope) {
|
||||||
global.category(DATA_LOADING);
|
global.start_category(DATA_LOADING);
|
||||||
global.define_func::<read>();
|
global.define_func::<read>();
|
||||||
global.define_func::<csv>();
|
global.define_func::<csv>();
|
||||||
global.define_func::<json>();
|
global.define_func::<json>();
|
||||||
|
@ -150,7 +150,7 @@ pub const WIDE: Em = Em::new(2.0);
|
|||||||
/// Create a module with all math definitions.
|
/// Create a module with all math definitions.
|
||||||
pub fn module() -> Module {
|
pub fn module() -> Module {
|
||||||
let mut math = Scope::deduplicating();
|
let mut math = Scope::deduplicating();
|
||||||
math.category(MATH);
|
math.start_category(MATH);
|
||||||
math.define_elem::<EquationElem>();
|
math.define_elem::<EquationElem>();
|
||||||
math.define_elem::<TextElem>();
|
math.define_elem::<TextElem>();
|
||||||
math.define_elem::<LrElem>();
|
math.define_elem::<LrElem>();
|
||||||
|
@ -52,7 +52,7 @@ pub static MODEL: Category;
|
|||||||
|
|
||||||
/// Hook up all `model` definitions.
|
/// Hook up all `model` definitions.
|
||||||
pub fn define(global: &mut Scope) {
|
pub fn define(global: &mut Scope) {
|
||||||
global.category(MODEL);
|
global.start_category(MODEL);
|
||||||
global.define_elem::<DocumentElem>();
|
global.define_elem::<DocumentElem>();
|
||||||
global.define_elem::<RefElem>();
|
global.define_elem::<RefElem>();
|
||||||
global.define_elem::<LinkElem>();
|
global.define_elem::<LinkElem>();
|
||||||
|
@ -12,7 +12,7 @@ pub static PDF: Category;
|
|||||||
|
|
||||||
/// Hook up the `pdf` module.
|
/// Hook up the `pdf` module.
|
||||||
pub(super) fn define(global: &mut Scope) {
|
pub(super) fn define(global: &mut Scope) {
|
||||||
global.category(PDF);
|
global.start_category(PDF);
|
||||||
global.define("pdf", module());
|
global.define("pdf", module());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ fn extend_scope_from_codex_module(scope: &mut Scope, module: codex::Module) {
|
|||||||
|
|
||||||
/// Hook up all `symbol` definitions.
|
/// Hook up all `symbol` definitions.
|
||||||
pub(super) fn define(global: &mut Scope) {
|
pub(super) fn define(global: &mut Scope) {
|
||||||
global.category(SYMBOLS);
|
global.start_category(SYMBOLS);
|
||||||
extend_scope_from_codex_module(global, codex::ROOT);
|
extend_scope_from_codex_module(global, codex::ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ pub static TEXT: Category;
|
|||||||
|
|
||||||
/// Hook up all `text` definitions.
|
/// Hook up all `text` definitions.
|
||||||
pub(super) fn define(global: &mut Scope) {
|
pub(super) fn define(global: &mut Scope) {
|
||||||
global.category(TEXT);
|
global.start_category(TEXT);
|
||||||
global.define_elem::<TextElem>();
|
global.define_elem::<TextElem>();
|
||||||
global.define_elem::<LinebreakElem>();
|
global.define_elem::<LinebreakElem>();
|
||||||
global.define_elem::<SmartQuoteElem>();
|
global.define_elem::<SmartQuoteElem>();
|
||||||
|
@ -36,7 +36,7 @@ pub static VISUALIZE: Category;
|
|||||||
|
|
||||||
/// Hook up all visualize definitions.
|
/// Hook up all visualize definitions.
|
||||||
pub(super) fn define(global: &mut Scope) {
|
pub(super) fn define(global: &mut Scope) {
|
||||||
global.category(VISUALIZE);
|
global.start_category(VISUALIZE);
|
||||||
global.define_type::<Color>();
|
global.define_type::<Color>();
|
||||||
global.define_type::<Gradient>();
|
global.define_type::<Gradient>();
|
||||||
global.define_type::<Tiling>();
|
global.define_type::<Tiling>();
|
||||||
|
@ -16,6 +16,7 @@ use serde::Deserialize;
|
|||||||
use serde_yaml as yaml;
|
use serde_yaml as yaml;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use typst::diag::{bail, StrResult};
|
use typst::diag::{bail, StrResult};
|
||||||
|
use typst::foundations::Binding;
|
||||||
use typst::foundations::{
|
use typst::foundations::{
|
||||||
AutoValue, Bytes, CastInfo, Category, Func, Module, NoneValue, ParamInfo, Repr,
|
AutoValue, Bytes, CastInfo, Category, Func, Module, NoneValue, ParamInfo, Repr,
|
||||||
Scope, Smart, Type, Value, FOUNDATIONS,
|
Scope, Smart, Type, Value, FOUNDATIONS,
|
||||||
@ -47,8 +48,8 @@ static GROUPS: LazyLock<Vec<GroupData>> = LazyLock::new(|| {
|
|||||||
.module()
|
.module()
|
||||||
.scope()
|
.scope()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, v, _)| matches!(v, Value::Func(_)))
|
.filter(|(_, b)| matches!(b.read(), Value::Func(_)))
|
||||||
.map(|(k, _, _)| k.clone())
|
.map(|(k, _)| k.clone())
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +61,7 @@ static LIBRARY: LazyLock<LazyHash<Library>> = LazyLock::new(|| {
|
|||||||
let scope = lib.global.scope_mut();
|
let scope = lib.global.scope_mut();
|
||||||
|
|
||||||
// Add those types, so that they show up in the docs.
|
// Add those types, so that they show up in the docs.
|
||||||
scope.category(FOUNDATIONS);
|
scope.start_category(FOUNDATIONS);
|
||||||
scope.define_type::<NoneValue>();
|
scope.define_type::<NoneValue>();
|
||||||
scope.define_type::<AutoValue>();
|
scope.define_type::<AutoValue>();
|
||||||
|
|
||||||
@ -270,8 +271,8 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
|
|||||||
|
|
||||||
// Add values and types.
|
// Add values and types.
|
||||||
let scope = module.scope();
|
let scope = module.scope();
|
||||||
for (name, value, _) in scope.iter() {
|
for (name, binding) in scope.iter() {
|
||||||
if scope.get_category(name) != Some(category) {
|
if binding.category() != Some(category) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +280,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
match value {
|
match binding.read() {
|
||||||
Value::Func(func) => {
|
Value::Func(func) => {
|
||||||
let name = func.name().unwrap();
|
let name = func.name().unwrap();
|
||||||
|
|
||||||
@ -476,8 +477,8 @@ fn casts(
|
|||||||
fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec<FuncModel> {
|
fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec<FuncModel> {
|
||||||
scope
|
scope
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(_, value, _)| {
|
.filter_map(|(_, binding)| {
|
||||||
let Value::Func(func) = value else { return None };
|
let Value::Func(func) = binding.read() else { return None };
|
||||||
Some(func_model(resolver, func, &[name], true))
|
Some(func_model(resolver, func, &[name], true))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@ -554,7 +555,7 @@ fn group_page(
|
|||||||
|
|
||||||
let mut outline_items = vec![];
|
let mut outline_items = vec![];
|
||||||
for name in &group.filter {
|
for name in &group.filter {
|
||||||
let value = group.module().scope().get(name).unwrap();
|
let value = group.module().scope().get(name).unwrap().read();
|
||||||
let Ok(ref func) = value.clone().cast::<Func>() else { panic!("not a function") };
|
let Ok(ref func) = value.clone().cast::<Func>() else { panic!("not a function") };
|
||||||
let func = func_model(resolver, func, &path, true);
|
let func = func_model(resolver, func, &path, true);
|
||||||
let id_base = urlify(&eco_format!("functions-{}", func.name));
|
let id_base = urlify(&eco_format!("functions-{}", func.name));
|
||||||
@ -662,8 +663,8 @@ fn symbols_page(resolver: &dyn Resolver, parent: &str, group: &GroupData) -> Pag
|
|||||||
/// Produce a symbol list's model.
|
/// Produce a symbol list's model.
|
||||||
fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
|
fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
|
||||||
let mut list = vec![];
|
let mut list = vec![];
|
||||||
for (name, value, _) in group.module().scope().iter() {
|
for (name, binding) in group.module().scope().iter() {
|
||||||
let Value::Symbol(symbol) = value else { continue };
|
let Value::Symbol(symbol) = binding.read() else { continue };
|
||||||
let complete = |variant: &str| {
|
let complete = |variant: &str| {
|
||||||
if variant.is_empty() {
|
if variant.is_empty() {
|
||||||
name.clone()
|
name.clone()
|
||||||
@ -703,7 +704,7 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
|
|||||||
/// Extract a module from another module.
|
/// Extract a module from another module.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn get_module<'a>(parent: &'a Module, name: &str) -> StrResult<&'a Module> {
|
fn get_module<'a>(parent: &'a Module, name: &str) -> StrResult<&'a Module> {
|
||||||
match parent.scope().get(name) {
|
match parent.scope().get(name).map(Binding::read) {
|
||||||
Some(Value::Module(module)) => Ok(module),
|
Some(Value::Module(module)) => Ok(module),
|
||||||
_ => bail!("module doesn't contain module `{name}`"),
|
_ => bail!("module doesn't contain module `{name}`"),
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use typst::diag::{bail, StrResult};
|
use typst::diag::{bail, StrResult};
|
||||||
use typst::foundations::Func;
|
use typst::foundations::{Binding, Func};
|
||||||
|
|
||||||
use crate::{get_module, GROUPS, LIBRARY};
|
use crate::{get_module, GROUPS, LIBRARY};
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ fn resolve_definition(head: &str, base: &str) -> StrResult<String> {
|
|||||||
|
|
||||||
while let Some(name) = parts.peek() {
|
while let Some(name) = parts.peek() {
|
||||||
if category.is_none() {
|
if category.is_none() {
|
||||||
category = focus.scope().get_category(name);
|
category = focus.scope().get(name).and_then(Binding::category);
|
||||||
}
|
}
|
||||||
let Ok(module) = get_module(focus, name) else { break };
|
let Ok(module) = get_module(focus, name) else { break };
|
||||||
focus = module;
|
focus = module;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user