mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Interpret methods on modules as functions in modules
This commit is contained in:
parent
6ca240508e
commit
2b8426b1b3
@ -54,11 +54,10 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Mutably borrow the value the given `key` maps to.
|
/// Mutably borrow the value the given `key` maps to.
|
||||||
///
|
pub fn at_mut(&mut self, key: &str) -> StrResult<&mut Value> {
|
||||||
/// This inserts the key with [`None`](Value::None) as the value if not
|
Arc::make_mut(&mut self.0)
|
||||||
/// present so far.
|
.get_mut(key)
|
||||||
pub fn at_mut(&mut self, key: Str) -> &mut Value {
|
.ok_or_else(|| missing_key(key))
|
||||||
Arc::make_mut(&mut self.0).entry(key).or_default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the value if the dictionary contains the given key.
|
/// Remove the value if the dictionary contains the given key.
|
||||||
|
@ -877,6 +877,18 @@ impl ast::Binary {
|
|||||||
op: fn(Value, Value) -> StrResult<Value>,
|
op: fn(Value, Value) -> StrResult<Value>,
|
||||||
) -> SourceResult<Value> {
|
) -> SourceResult<Value> {
|
||||||
let rhs = self.rhs().eval(vm)?;
|
let rhs = self.rhs().eval(vm)?;
|
||||||
|
let lhs = self.lhs();
|
||||||
|
|
||||||
|
// An assignment to a dictionary field is different from a normal access
|
||||||
|
// since it can create the field instead of just modifying it.
|
||||||
|
if self.op() == ast::BinOp::Assign {
|
||||||
|
if let ast::Expr::FieldAccess(access) = &lhs {
|
||||||
|
let dict = access.access_dict(vm)?;
|
||||||
|
dict.insert(access.field().take().into(), rhs);
|
||||||
|
return Ok(Value::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let location = self.lhs().access(vm)?;
|
let location = self.lhs().access(vm)?;
|
||||||
let lhs = std::mem::take(&mut *location);
|
let lhs = std::mem::take(&mut *location);
|
||||||
*location = op(lhs, rhs).at(self.span())?;
|
*location = op(lhs, rhs).at(self.span())?;
|
||||||
@ -898,14 +910,7 @@ impl Eval for ast::FieldAccess {
|
|||||||
.field(&field)
|
.field(&field)
|
||||||
.ok_or_else(|| format!("unknown field `{field}`"))
|
.ok_or_else(|| format!("unknown field `{field}`"))
|
||||||
.at(span)?,
|
.at(span)?,
|
||||||
Value::Module(module) => module
|
Value::Module(module) => module.get(&field).cloned().at(span)?,
|
||||||
.scope()
|
|
||||||
.get(&field)
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
format!("module `{}` does not contain `{field}`", module.name())
|
|
||||||
})
|
|
||||||
.at(span)?,
|
|
||||||
v => bail!(
|
v => bail!(
|
||||||
self.target().span(),
|
self.target().span(),
|
||||||
"expected dictionary or content, found {}",
|
"expected dictionary or content, found {}",
|
||||||
@ -921,7 +926,8 @@ impl Eval for ast::FuncCall {
|
|||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let callee = self.callee();
|
let callee = self.callee();
|
||||||
let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?;
|
let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?;
|
||||||
self.eval_with_callee(vm, callee)
|
let args = self.args().eval(vm)?;
|
||||||
|
Self::eval_call(vm, &callee, args, self.span())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -929,7 +935,8 @@ impl ast::FuncCall {
|
|||||||
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
|
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||||
let callee = self.callee().eval(vm)?;
|
let callee = self.callee().eval(vm)?;
|
||||||
if let Value::Func(callee) = callee {
|
if let Value::Func(callee) = callee {
|
||||||
Ok(self.eval_with_callee(vm, callee)?.display_in_math())
|
let args = self.args().eval(vm)?;
|
||||||
|
Ok(Self::eval_call(vm, &callee, args, self.span())?.display_in_math())
|
||||||
} else {
|
} else {
|
||||||
let mut body = (vm.items.math_atom)('('.into());
|
let mut body = (vm.items.math_atom)('('.into());
|
||||||
let mut args = self.args().eval(vm)?;
|
let mut args = self.args().eval(vm)?;
|
||||||
@ -944,14 +951,18 @@ impl ast::FuncCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_with_callee(&self, vm: &mut Vm, callee: Func) -> SourceResult<Value> {
|
fn eval_call(
|
||||||
|
vm: &mut Vm,
|
||||||
|
callee: &Func,
|
||||||
|
args: Args,
|
||||||
|
span: Span,
|
||||||
|
) -> SourceResult<Value> {
|
||||||
if vm.depth >= MAX_CALL_DEPTH {
|
if vm.depth >= MAX_CALL_DEPTH {
|
||||||
bail!(self.span(), "maximum function call depth exceeded");
|
bail!(span, "maximum function call depth exceeded");
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = self.args().eval(vm)?;
|
|
||||||
let point = || Tracepoint::Call(callee.name().map(Into::into));
|
let point = || Tracepoint::Call(callee.name().map(Into::into));
|
||||||
callee.call(vm, args).trace(vm.world, point, self.span())
|
callee.call(vm, args).trace(vm.world, point, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -960,18 +971,35 @@ impl Eval for ast::MethodCall {
|
|||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let span = self.span();
|
let span = self.span();
|
||||||
let method = self.method().take();
|
let method = self.method();
|
||||||
|
|
||||||
let result = if methods::is_mutating(&method) {
|
let result = if methods::is_mutating(&method) {
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
let value = self.target().access(vm)?;
|
let value = self.target().access(vm)?;
|
||||||
|
|
||||||
|
if let Value::Module(module) = &value {
|
||||||
|
if let Value::Func(callee) =
|
||||||
|
module.get(&method).cloned().at(method.span())?
|
||||||
|
{
|
||||||
|
return ast::FuncCall::eval_call(vm, &callee, args, self.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
methods::call_mut(value, &method, args, span)
|
methods::call_mut(value, &method, args, span)
|
||||||
} else {
|
} else {
|
||||||
let value = self.target().eval(vm)?;
|
let value = self.target().eval(vm)?;
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
|
|
||||||
|
if let Value::Module(module) = &value {
|
||||||
|
if let Value::Func(callee) = module.get(&method).at(method.span())? {
|
||||||
|
return ast::FuncCall::eval_call(vm, callee, args, self.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
methods::call(vm, value, &method, args, span)
|
methods::call(vm, value, &method, args, span)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let method = method.take();
|
||||||
let point = || Tracepoint::Call(Some(method.clone()));
|
let point = || Tracepoint::Call(Some(method.clone()));
|
||||||
result.trace(vm.world, point, span)
|
result.trace(vm.world, point, span)
|
||||||
}
|
}
|
||||||
@ -1423,16 +1451,20 @@ impl Access for ast::Parenthesized {
|
|||||||
|
|
||||||
impl Access for ast::FieldAccess {
|
impl Access for ast::FieldAccess {
|
||||||
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 value = self.target().access(vm)?;
|
self.access_dict(vm)?.at_mut(&self.field().take()).at(self.span())
|
||||||
let Value::Dict(dict) = value else {
|
}
|
||||||
bail!(
|
}
|
||||||
|
|
||||||
|
impl ast::FieldAccess {
|
||||||
|
fn access_dict<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Dict> {
|
||||||
|
match self.target().access(vm)? {
|
||||||
|
Value::Dict(dict) => Ok(dict),
|
||||||
|
value => bail!(
|
||||||
self.target().span(),
|
self.target().span(),
|
||||||
"expected dictionary, found {}",
|
"expected dictionary, found {}",
|
||||||
value.type_name(),
|
value.type_name(),
|
||||||
);
|
),
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(dict.at_mut(self.field().take().into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +153,7 @@ pub fn call_mut(
|
|||||||
},
|
},
|
||||||
|
|
||||||
Value::Dict(dict) => match method {
|
Value::Dict(dict) => match method {
|
||||||
|
"insert" => dict.insert(args.expect::<Str>("key")?, args.expect("value")?),
|
||||||
"remove" => {
|
"remove" => {
|
||||||
output = dict.remove(&args.expect::<EcoString>("key")?).at(span)?
|
output = dict.remove(&args.expect::<EcoString>("key")?).at(span)?
|
||||||
}
|
}
|
||||||
@ -184,7 +185,7 @@ pub fn call_access<'a>(
|
|||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
},
|
},
|
||||||
Value::Dict(dict) => match method {
|
Value::Dict(dict) => match method {
|
||||||
"at" => dict.at_mut(args.expect("index")?),
|
"at" => dict.at_mut(&args.expect::<Str>("key")?).at(span)?,
|
||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
},
|
},
|
||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
|
@ -2,8 +2,9 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{Content, Scope};
|
use super::{Content, Scope, Value};
|
||||||
use crate::util::EcoString;
|
use crate::diag::StrResult;
|
||||||
|
use crate::util::{format_eco, EcoString};
|
||||||
|
|
||||||
/// An evaluated module, ready for importing or typesetting.
|
/// An evaluated module, ready for importing or typesetting.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
@ -46,6 +47,13 @@ impl Module {
|
|||||||
&self.0.scope
|
&self.0.scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to access a definition in the module.
|
||||||
|
pub fn get(&self, name: &str) -> StrResult<&Value> {
|
||||||
|
self.scope().get(&name).ok_or_else(|| {
|
||||||
|
format_eco!("module `{}` does not contain `{name}`", self.name())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract the module's content.
|
/// Extract the module's content.
|
||||||
pub fn content(self) -> Content {
|
pub fn content(self) -> Content {
|
||||||
match Arc::try_unwrap(self.0) {
|
match Arc::try_unwrap(self.0) {
|
||||||
|
@ -36,11 +36,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Missing lvalue is automatically none-initialized.
|
// Missing lvalue is not automatically none-initialized.
|
||||||
{
|
{
|
||||||
let dict = (:)
|
let dict = (:)
|
||||||
dict.at("b") += 1
|
// Error: 3-9 dictionary does not contain key "b"
|
||||||
test(dict, (b: 1))
|
dict.b += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -38,7 +38,19 @@
|
|||||||
// A module import without items.
|
// A module import without items.
|
||||||
#import "module.typ"
|
#import "module.typ"
|
||||||
#test(module.b, 1)
|
#test(module.b, 1)
|
||||||
#test((module.item)(1, 2), 3)
|
#test(module.item(1, 2), 3)
|
||||||
|
#test(module.push(2), 3)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Edge case for module access that isn't fixed.
|
||||||
|
#import "module.typ"
|
||||||
|
|
||||||
|
// Works because the method name isn't categorized as mutating.
|
||||||
|
#test((module,).at(0).item(1, 2), 3)
|
||||||
|
|
||||||
|
// Doesn't work because of mutating name.
|
||||||
|
// Error: 2-11 cannot mutate a temporary value
|
||||||
|
{(module,).at(0).push()}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Who needs whitespace anyways?
|
// Who needs whitespace anyways?
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#let d = 3
|
#let d = 3
|
||||||
#let value = [hi]
|
#let value = [hi]
|
||||||
#let item(a, b) = a + b
|
#let item(a, b) = a + b
|
||||||
|
#let push(a) = a + 1
|
||||||
#let fn = rect.with(fill: conifer, inset: 5pt)
|
#let fn = rect.with(fill: conifer, inset: 5pt)
|
||||||
|
|
||||||
Some _includable_ text.
|
Some _includable_ text.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user