mirror of
https://github.com/typst/typst
synced 2025-05-15 01:25:28 +08:00
104 lines
3.5 KiB
Rust
104 lines
3.5 KiB
Rust
use ecow::eco_format;
|
|
use typst_library::diag::{bail, At, Hint, SourceResult, Trace, Tracepoint};
|
|
use typst_library::foundations::{Dict, Value};
|
|
use typst_syntax::ast::{self, AstNode};
|
|
|
|
use crate::{call_method_access, is_accessor_method, Eval, Vm};
|
|
|
|
/// Access an expression mutably.
|
|
pub(crate) trait Access {
|
|
/// Access the expression's evaluated value mutably.
|
|
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value>;
|
|
}
|
|
|
|
impl Access for ast::Expr<'_> {
|
|
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
|
match self {
|
|
Self::Ident(v) => v.access(vm),
|
|
Self::Parenthesized(v) => v.access(vm),
|
|
Self::FieldAccess(v) => v.access(vm),
|
|
Self::FuncCall(v) => v.access(vm),
|
|
_ => {
|
|
let _ = self.eval(vm)?;
|
|
bail!(self.span(), "cannot mutate a temporary value");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Access for ast::Ident<'_> {
|
|
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
|
let span = self.span();
|
|
if vm.inspected == Some(span) {
|
|
if let Ok(binding) = vm.scopes.get(&self) {
|
|
vm.trace(binding.read().clone());
|
|
}
|
|
}
|
|
vm.scopes
|
|
.get_mut(&self)
|
|
.and_then(|b| b.write().map_err(Into::into))
|
|
.at(span)
|
|
}
|
|
}
|
|
|
|
impl Access for ast::Parenthesized<'_> {
|
|
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
|
self.expr().access(vm)
|
|
}
|
|
}
|
|
|
|
impl Access for ast::FieldAccess<'_> {
|
|
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
|
access_dict(vm, self)?.at_mut(self.field().get()).at(self.span())
|
|
}
|
|
}
|
|
|
|
impl Access for ast::FuncCall<'_> {
|
|
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
|
if let ast::Expr::FieldAccess(access) = self.callee() {
|
|
let method = access.field();
|
|
if is_accessor_method(&method) {
|
|
let span = self.span();
|
|
let world = vm.world();
|
|
let args = self.args().eval(vm)?.spanned(span);
|
|
let value = access.target().access(vm)?;
|
|
let result = call_method_access(value, &method, args, span);
|
|
let point = || Tracepoint::Call(Some(method.get().clone()));
|
|
return result.trace(world, point, span);
|
|
}
|
|
}
|
|
|
|
let _ = self.eval(vm)?;
|
|
bail!(self.span(), "cannot mutate a temporary value");
|
|
}
|
|
}
|
|
|
|
pub(crate) fn access_dict<'a>(
|
|
vm: &'a mut Vm,
|
|
access: ast::FieldAccess,
|
|
) -> SourceResult<&'a mut Dict> {
|
|
match access.target().access(vm)? {
|
|
Value::Dict(dict) => Ok(dict),
|
|
value => {
|
|
let ty = value.ty();
|
|
let span = access.target().span();
|
|
if matches!(
|
|
value, // those types have their own field getters
|
|
Value::Symbol(_) | Value::Content(_) | Value::Module(_) | Value::Func(_)
|
|
) {
|
|
bail!(span, "cannot mutate fields on {ty}");
|
|
} else if typst_library::foundations::fields_on(ty).is_empty() {
|
|
bail!(span, "{ty} does not have accessible fields");
|
|
} else {
|
|
// type supports static fields, which don't yet have
|
|
// setters
|
|
Err(eco_format!("fields on {ty} are not yet mutable"))
|
|
.hint(eco_format!(
|
|
"try creating a new {ty} with the updated field value instead"
|
|
))
|
|
.at(span)
|
|
}
|
|
}
|
|
}
|
|
}
|