mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Pure functions!
This commit is contained in:
parent
22214a1e0a
commit
806d9f0d9a
83
Cargo.lock
generated
83
Cargo.lock
generated
@ -433,16 +433,6 @@ dependencies = [
|
|||||||
"rand_chacha 0.3.1",
|
"rand_chacha 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.17"
|
version = "0.4.17"
|
||||||
@ -568,29 +558,6 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.9.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathfinder_color"
|
name = "pathfinder_color"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -943,12 +910,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -1177,7 +1138,6 @@ dependencies = [
|
|||||||
"memmap2",
|
"memmap2",
|
||||||
"miniz_oxide 0.4.4",
|
"miniz_oxide 0.4.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
|
||||||
"pdf-writer",
|
"pdf-writer",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"pixglyph",
|
"pixglyph",
|
||||||
@ -1420,49 +1380,6 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xi-unicode"
|
name = "xi-unicode"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -21,7 +21,6 @@ lipsum = { git = "https://github.com/reknih/lipsum", default-features = false }
|
|||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
typed-arena = "2"
|
typed-arena = "2"
|
||||||
parking_lot = "0.12"
|
|
||||||
unscanny = { git = "https://github.com/typst/unscanny" }
|
unscanny = { git = "https://github.com/typst/unscanny" }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::{Scope, Scopes, Value};
|
use super::{Scope, Scopes, Value};
|
||||||
use crate::syntax::ast::{ClosureParam, Expr, Ident, Imports, TypedNode};
|
use crate::syntax::ast::{ClosureParam, Expr, Ident, Imports, TypedNode};
|
||||||
use crate::syntax::RedRef;
|
use crate::syntax::RedRef;
|
||||||
@ -28,14 +26,14 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
|
|
||||||
/// Bind a new internal variable.
|
/// Bind a new internal variable.
|
||||||
pub fn bind(&mut self, ident: Ident) {
|
pub fn bind(&mut self, ident: Ident) {
|
||||||
self.internal.top.def_mut(ident.take(), Value::None);
|
self.internal.top.define(ident.take(), Value::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Capture a variable if it isn't internal.
|
/// Capture a variable if it isn't internal.
|
||||||
pub fn capture(&mut self, ident: Ident) {
|
pub fn capture(&mut self, ident: Ident) {
|
||||||
if self.internal.get(&ident).is_none() {
|
if self.internal.get(&ident).is_err() {
|
||||||
if let Some(slot) = self.external.get(&ident) {
|
if let Ok(value) = self.external.get(&ident) {
|
||||||
self.captures.def_slot(ident.take(), Arc::clone(slot));
|
self.captures.define_captured(ident.take(), value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,9 +143,9 @@ mod tests {
|
|||||||
let red = RedNode::from_root(green, SourceId::from_raw(0));
|
let red = RedNode::from_root(green, SourceId::from_raw(0));
|
||||||
|
|
||||||
let mut scopes = Scopes::new(None);
|
let mut scopes = Scopes::new(None);
|
||||||
scopes.top.def_const("x", 0);
|
scopes.top.define("x", 0);
|
||||||
scopes.top.def_const("y", 0);
|
scopes.top.define("y", 0);
|
||||||
scopes.top.def_const("z", 0);
|
scopes.top.define("z", 0);
|
||||||
|
|
||||||
let mut visitor = CapturesVisitor::new(&scopes);
|
let mut visitor = CapturesVisitor::new(&scopes);
|
||||||
visitor.visit(red.as_ref());
|
visitor.visit(red.as_ref());
|
||||||
|
@ -206,7 +206,7 @@ impl Closure {
|
|||||||
|
|
||||||
// Parse the arguments according to the parameter list.
|
// Parse the arguments according to the parameter list.
|
||||||
for (param, default) in &self.params {
|
for (param, default) in &self.params {
|
||||||
scopes.top.def_mut(param, match default {
|
scopes.top.define(param, match default {
|
||||||
None => args.expect::<Value>(param)?,
|
None => args.expect::<Value>(param)?,
|
||||||
Some(default) => {
|
Some(default) => {
|
||||||
args.named::<Value>(param)?.unwrap_or_else(|| default.clone())
|
args.named::<Value>(param)?.unwrap_or_else(|| default.clone())
|
||||||
@ -216,7 +216,7 @@ impl Closure {
|
|||||||
|
|
||||||
// Put the remaining arguments into the sink.
|
// Put the remaining arguments into the sink.
|
||||||
if let Some(sink) = &self.sink {
|
if let Some(sink) = &self.sink {
|
||||||
scopes.top.def_mut(sink, args.take());
|
scopes.top.define(sink, args.take());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the route inside the closure.
|
// Determine the route inside the closure.
|
||||||
|
100
src/eval/mod.rs
100
src/eval/mod.rs
@ -31,7 +31,6 @@ pub use value::*;
|
|||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard};
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult};
|
use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult};
|
||||||
@ -165,7 +164,7 @@ fn eval_markup(
|
|||||||
}
|
}
|
||||||
MarkupNode::Expr(Expr::Wrap(wrap)) => {
|
MarkupNode::Expr(Expr::Wrap(wrap)) => {
|
||||||
let tail = eval_markup(vm, nodes)?;
|
let tail = eval_markup(vm, nodes)?;
|
||||||
vm.scopes.top.def_mut(wrap.binding().take(), tail);
|
vm.scopes.top.define(wrap.binding().take(), tail);
|
||||||
wrap.body().eval(vm)?.display()
|
wrap.body().eval(vm)?.display()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,10 +353,7 @@ impl Eval for Ident {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
||||||
match vm.scopes.get(self) {
|
vm.scopes.get(self).cloned().at(self.span())
|
||||||
Some(slot) => Ok(slot.read().clone()),
|
|
||||||
None => bail!(self.span(), "unknown variable"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +400,7 @@ fn eval_code(
|
|||||||
}
|
}
|
||||||
Expr::Wrap(wrap) => {
|
Expr::Wrap(wrap) => {
|
||||||
let tail = eval_code(vm, exprs)?;
|
let tail = eval_code(vm, exprs)?;
|
||||||
vm.scopes.top.def_mut(wrap.binding().take(), tail);
|
vm.scopes.top.define(wrap.binding().take(), tail);
|
||||||
wrap.body().eval(vm)?
|
wrap.body().eval(vm)?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,8 +561,7 @@ impl BinaryExpr {
|
|||||||
op: fn(Value, Value) -> StrResult<Value>,
|
op: fn(Value, Value) -> StrResult<Value>,
|
||||||
) -> TypResult<Value> {
|
) -> TypResult<Value> {
|
||||||
let rhs = self.rhs().eval(vm)?;
|
let rhs = self.rhs().eval(vm)?;
|
||||||
let lhs = self.lhs();
|
let location = self.lhs().access(vm)?;
|
||||||
let mut location = 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())?;
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
@ -748,7 +743,7 @@ impl Eval for LetExpr {
|
|||||||
Some(expr) => expr.eval(vm)?,
|
Some(expr) => expr.eval(vm)?,
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
};
|
};
|
||||||
vm.scopes.top.def_mut(self.binding().take(), value);
|
vm.scopes.top.define(self.binding().take(), value);
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -860,7 +855,7 @@ impl Eval for ForExpr {
|
|||||||
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
|
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
|
||||||
#[allow(unused_parens)]
|
#[allow(unused_parens)]
|
||||||
for ($($value),*) in $iter {
|
for ($($value),*) in $iter {
|
||||||
$(vm.scopes.top.def_mut(&$binding, $value);)*
|
$(vm.scopes.top.define(&$binding, $value);)*
|
||||||
|
|
||||||
let body = self.body();
|
let body = self.body();
|
||||||
let value = body.eval(vm)?;
|
let value = body.eval(vm)?;
|
||||||
@ -937,14 +932,14 @@ impl Eval for ImportExpr {
|
|||||||
|
|
||||||
match self.imports() {
|
match self.imports() {
|
||||||
Imports::Wildcard => {
|
Imports::Wildcard => {
|
||||||
for (var, slot) in module.scope.iter() {
|
for (var, value) in module.scope.iter() {
|
||||||
vm.scopes.top.def_mut(var, slot.read().clone());
|
vm.scopes.top.define(var, value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Imports::Items(idents) => {
|
Imports::Items(idents) => {
|
||||||
for ident in idents {
|
for ident in idents {
|
||||||
if let Some(slot) = module.scope.get(&ident) {
|
if let Some(value) = module.scope.get(&ident) {
|
||||||
vm.scopes.top.def_mut(ident.take(), slot.read().clone());
|
vm.scopes.top.define(ident.take(), value.clone());
|
||||||
} else {
|
} else {
|
||||||
bail!(ident.span(), "unresolved import");
|
bail!(ident.span(), "unresolved import");
|
||||||
}
|
}
|
||||||
@ -1028,11 +1023,11 @@ impl Eval for ReturnExpr {
|
|||||||
/// Access an expression mutably.
|
/// Access an expression mutably.
|
||||||
pub trait Access {
|
pub trait Access {
|
||||||
/// Access the value.
|
/// Access the value.
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>>;
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for Expr {
|
impl Access for Expr {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>> {
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
||||||
match self {
|
match self {
|
||||||
Expr::Ident(v) => v.access(vm),
|
Expr::Ident(v) => v.access(vm),
|
||||||
Expr::FieldAccess(v) => v.access(vm),
|
Expr::FieldAccess(v) => v.access(vm),
|
||||||
@ -1043,68 +1038,35 @@ impl Access for Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Access for Ident {
|
impl Access for Ident {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>> {
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
||||||
match vm.scopes.get(self) {
|
vm.scopes.get_mut(self).at(self.span())
|
||||||
Some(slot) => match slot.try_write() {
|
|
||||||
Some(guard) => Ok(RwLockWriteGuard::map(guard, |v| v)),
|
|
||||||
None => bail!(self.span(), "cannot mutate a constant"),
|
|
||||||
},
|
|
||||||
None => bail!(self.span(), "unknown variable"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for FieldAccess {
|
impl Access for FieldAccess {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>> {
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
||||||
let guard = self.object().access(vm)?;
|
Ok(match self.object().access(vm)? {
|
||||||
try_map(guard, |value| {
|
Value::Dict(dict) => dict.get_mut(self.field().take()),
|
||||||
Ok(match value {
|
v => bail!(
|
||||||
Value::Dict(dict) => dict.get_mut(self.field().take()),
|
self.object().span(),
|
||||||
v => bail!(
|
"expected dictionary, found {}",
|
||||||
self.object().span(),
|
v.type_name(),
|
||||||
"expected dictionary, found {}",
|
),
|
||||||
v.type_name(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for FuncCall {
|
impl Access for FuncCall {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>> {
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
let guard = self.callee().access(vm)?;
|
Ok(match self.callee().access(vm)? {
|
||||||
try_map(guard, |value| {
|
Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?,
|
||||||
Ok(match value {
|
Value::Dict(dict) => dict.get_mut(args.into_key()?),
|
||||||
Value::Array(array) => {
|
v => bail!(
|
||||||
array.get_mut(args.into_index()?).at(self.span())?
|
self.callee().span(),
|
||||||
}
|
"expected collection, found {}",
|
||||||
Value::Dict(dict) => dict.get_mut(args.into_key()?),
|
v.type_name(),
|
||||||
v => bail!(
|
),
|
||||||
self.callee().span(),
|
|
||||||
"expected collection, found {}",
|
|
||||||
v.type_name(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mutable location.
|
|
||||||
type Location<'a> = MappedRwLockWriteGuard<'a, Value>;
|
|
||||||
|
|
||||||
/// Map a reader-writer lock with a function.
|
|
||||||
fn try_map<F>(location: Location, f: F) -> TypResult<Location>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Value) -> TypResult<&mut Value>,
|
|
||||||
{
|
|
||||||
let mut error = None;
|
|
||||||
MappedRwLockWriteGuard::try_map(location, |value| match f(value) {
|
|
||||||
Ok(value) => Some(value),
|
|
||||||
Err(err) => {
|
|
||||||
error = Some(err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(|_| error.unwrap())
|
|
||||||
}
|
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::Hash;
|
||||||
use std::iter;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
|
|
||||||
use super::{Args, Func, Machine, Node, Value};
|
use super::{Args, Func, Machine, Node, Value};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::{StrResult, TypResult};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// A slot where a variable is stored.
|
|
||||||
pub type Slot = Arc<RwLock<Value>>;
|
|
||||||
|
|
||||||
/// A stack of scopes.
|
/// A stack of scopes.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct Scopes<'a> {
|
pub struct Scopes<'a> {
|
||||||
@ -42,21 +35,33 @@ impl<'a> Scopes<'a> {
|
|||||||
self.top = self.scopes.pop().expect("no pushed scope");
|
self.top = self.scopes.pop().expect("no pushed scope");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the slot of a variable.
|
/// Try to access a variable immutably.
|
||||||
pub fn get(&self, var: &str) -> Option<&Slot> {
|
pub fn get(&self, var: &str) -> StrResult<&Value> {
|
||||||
iter::once(&self.top)
|
Ok(std::iter::once(&self.top)
|
||||||
.chain(self.scopes.iter().rev())
|
.chain(self.scopes.iter().rev())
|
||||||
.chain(self.base.into_iter())
|
.chain(self.base.into_iter())
|
||||||
.find_map(|scope| scope.get(var))
|
.find_map(|scope| scope.get(var))
|
||||||
|
.ok_or("unknown variable")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to access a variable mutably.
|
||||||
|
pub fn get_mut(&mut self, var: &str) -> StrResult<&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(|| {
|
||||||
|
if self.base.map_or(false, |base| base.get(var).is_some()) {
|
||||||
|
"cannot mutate a constant"
|
||||||
|
} else {
|
||||||
|
"unknown variable"
|
||||||
|
}
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map from variable names to variable slots.
|
/// A map from binding names to values.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone, Hash)]
|
||||||
pub struct Scope {
|
pub struct Scope(BTreeMap<EcoString, Slot>);
|
||||||
/// The mapping from names to slots.
|
|
||||||
values: BTreeMap<EcoString, Slot>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
/// Create a new empty scope.
|
/// Create a new empty scope.
|
||||||
@ -64,24 +69,9 @@ impl Scope {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a constant variable with a value.
|
/// Bind a value to a name.
|
||||||
pub fn def_const(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
|
pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) {
|
||||||
let cell = RwLock::new(value.into());
|
self.0.insert(name.into(), Slot::new(value.into(), Kind::Normal));
|
||||||
|
|
||||||
// Make it impossible to write to this value again.
|
|
||||||
std::mem::forget(cell.read());
|
|
||||||
|
|
||||||
self.values.insert(var.into(), Arc::new(cell));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a mutable variable with a value.
|
|
||||||
pub fn def_mut(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
|
|
||||||
self.values.insert(var.into(), Arc::new(RwLock::new(value.into())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a variable with a slot.
|
|
||||||
pub fn def_slot(&mut self, var: impl Into<EcoString>, slot: Slot) {
|
|
||||||
self.values.insert(var.into(), slot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a function through a native rust function.
|
/// Define a function through a native rust function.
|
||||||
@ -90,32 +80,36 @@ impl Scope {
|
|||||||
name: &'static str,
|
name: &'static str,
|
||||||
func: fn(&mut Machine, &mut Args) -> TypResult<Value>,
|
func: fn(&mut Machine, &mut Args) -> TypResult<Value>,
|
||||||
) {
|
) {
|
||||||
self.def_const(name, Func::from_fn(name, func));
|
self.define(name, Func::from_fn(name, func));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a function through a native rust node.
|
/// Define a function through a native rust node.
|
||||||
pub fn def_node<T: Node>(&mut self, name: &'static str) {
|
pub fn def_node<T: Node>(&mut self, name: &'static str) {
|
||||||
self.def_const(name, Func::from_node::<T>(name));
|
self.define(name, Func::from_node::<T>(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the value of a variable.
|
/// Define a captured, immutable binding.
|
||||||
pub fn get(&self, var: &str) -> Option<&Slot> {
|
pub fn define_captured(
|
||||||
self.values.get(var)
|
&mut self,
|
||||||
|
var: impl Into<EcoString>,
|
||||||
|
value: impl Into<Value>,
|
||||||
|
) {
|
||||||
|
self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to access a variable immutably.
|
||||||
|
pub fn get(&self, var: &str) -> Option<&Value> {
|
||||||
|
self.0.get(var).map(Slot::read)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to access a variable mutably.
|
||||||
|
pub fn get_mut(&mut self, var: &str) -> Option<StrResult<&mut Value>> {
|
||||||
|
self.0.get_mut(var).map(Slot::write)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all definitions.
|
/// Iterate over all definitions.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Slot)> {
|
pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
|
||||||
self.values.iter().map(|(k, v)| (k.as_str(), v))
|
self.0.iter().map(|(k, v)| (k.as_str(), v.read()))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for Scope {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.values.len().hash(state);
|
|
||||||
for (name, value) in self.values.iter() {
|
|
||||||
name.hash(state);
|
|
||||||
value.read().hash(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +117,45 @@ impl Debug for Scope {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_str("Scope ")?;
|
f.write_str("Scope ")?;
|
||||||
f.debug_map()
|
f.debug_map()
|
||||||
.entries(self.values.iter().map(|(k, v)| (k, v.read())))
|
.entries(self.0.iter().map(|(k, v)| (k, v.read())))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A slot where a variable is stored.
|
||||||
|
#[derive(Clone, Hash)]
|
||||||
|
struct Slot {
|
||||||
|
/// The stored value.
|
||||||
|
value: Value,
|
||||||
|
/// The kind of slot, determines how the value can be accessed.
|
||||||
|
kind: Kind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The different kinds of slots.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
enum Kind {
|
||||||
|
/// A normal, mutable binding.
|
||||||
|
Normal,
|
||||||
|
/// A captured copy of another variable.
|
||||||
|
Captured,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Slot {
|
||||||
|
/// Create a new constant slot.
|
||||||
|
fn new(value: Value, kind: Kind) -> Self {
|
||||||
|
Self { value, kind }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the variable.
|
||||||
|
fn read(&self) -> &Value {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to write to the variable.
|
||||||
|
fn write(&mut self) -> StrResult<&mut Value> {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Normal => Ok(&mut self.value),
|
||||||
|
Kind::Captured => Err("cannot mutate a captured variable")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -96,38 +96,38 @@ pub fn new() -> Scope {
|
|||||||
std.def_fn("lorem", utility::lorem);
|
std.def_fn("lorem", utility::lorem);
|
||||||
|
|
||||||
// Predefined colors.
|
// Predefined colors.
|
||||||
std.def_const("black", Color::BLACK);
|
std.define("black", Color::BLACK);
|
||||||
std.def_const("gray", Color::GRAY);
|
std.define("gray", Color::GRAY);
|
||||||
std.def_const("silver", Color::SILVER);
|
std.define("silver", Color::SILVER);
|
||||||
std.def_const("white", Color::WHITE);
|
std.define("white", Color::WHITE);
|
||||||
std.def_const("navy", Color::NAVY);
|
std.define("navy", Color::NAVY);
|
||||||
std.def_const("blue", Color::BLUE);
|
std.define("blue", Color::BLUE);
|
||||||
std.def_const("aqua", Color::AQUA);
|
std.define("aqua", Color::AQUA);
|
||||||
std.def_const("teal", Color::TEAL);
|
std.define("teal", Color::TEAL);
|
||||||
std.def_const("eastern", Color::EASTERN);
|
std.define("eastern", Color::EASTERN);
|
||||||
std.def_const("purple", Color::PURPLE);
|
std.define("purple", Color::PURPLE);
|
||||||
std.def_const("fuchsia", Color::FUCHSIA);
|
std.define("fuchsia", Color::FUCHSIA);
|
||||||
std.def_const("maroon", Color::MAROON);
|
std.define("maroon", Color::MAROON);
|
||||||
std.def_const("red", Color::RED);
|
std.define("red", Color::RED);
|
||||||
std.def_const("orange", Color::ORANGE);
|
std.define("orange", Color::ORANGE);
|
||||||
std.def_const("yellow", Color::YELLOW);
|
std.define("yellow", Color::YELLOW);
|
||||||
std.def_const("olive", Color::OLIVE);
|
std.define("olive", Color::OLIVE);
|
||||||
std.def_const("green", Color::GREEN);
|
std.define("green", Color::GREEN);
|
||||||
std.def_const("lime", Color::LIME);
|
std.define("lime", Color::LIME);
|
||||||
|
|
||||||
// Other constants.
|
// Other constants.
|
||||||
std.def_const("ltr", Dir::LTR);
|
std.define("ltr", Dir::LTR);
|
||||||
std.def_const("rtl", Dir::RTL);
|
std.define("rtl", Dir::RTL);
|
||||||
std.def_const("ttb", Dir::TTB);
|
std.define("ttb", Dir::TTB);
|
||||||
std.def_const("btt", Dir::BTT);
|
std.define("btt", Dir::BTT);
|
||||||
std.def_const("start", RawAlign::Start);
|
std.define("start", RawAlign::Start);
|
||||||
std.def_const("end", RawAlign::End);
|
std.define("end", RawAlign::End);
|
||||||
std.def_const("left", RawAlign::Specific(Align::Left));
|
std.define("left", RawAlign::Specific(Align::Left));
|
||||||
std.def_const("center", RawAlign::Specific(Align::Center));
|
std.define("center", RawAlign::Specific(Align::Center));
|
||||||
std.def_const("right", RawAlign::Specific(Align::Right));
|
std.define("right", RawAlign::Specific(Align::Right));
|
||||||
std.def_const("top", RawAlign::Specific(Align::Top));
|
std.define("top", RawAlign::Specific(Align::Top));
|
||||||
std.def_const("horizon", RawAlign::Specific(Align::Horizon));
|
std.define("horizon", RawAlign::Specific(Align::Horizon));
|
||||||
std.def_const("bottom", RawAlign::Specific(Align::Bottom));
|
std.define("bottom", RawAlign::Specific(Align::Bottom));
|
||||||
|
|
||||||
std
|
std
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
---
|
---
|
||||||
// Capture environment.
|
// Capture environment.
|
||||||
{
|
{
|
||||||
let mark = "?"
|
let mark = "!"
|
||||||
let greet = {
|
let greet = {
|
||||||
let hi = "Hi"
|
let hi = "Hi"
|
||||||
name => {
|
name => {
|
||||||
@ -39,9 +39,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test(greet("Typst"), "Hi, Typst?")
|
test(greet("Typst"), "Hi, Typst!")
|
||||||
|
|
||||||
mark = "!"
|
// Changing the captured variable after the closure definition has no effect.
|
||||||
|
mark = "?"
|
||||||
test(greet("Typst"), "Hi, Typst!")
|
test(greet("Typst"), "Hi, Typst!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,12 +72,12 @@
|
|||||||
// For loop bindings.
|
// For loop bindings.
|
||||||
{
|
{
|
||||||
let v = (1, 2, 3)
|
let v = (1, 2, 3)
|
||||||
let s = 0
|
|
||||||
let f() = {
|
let f() = {
|
||||||
|
let s = 0
|
||||||
for v in v { s += v }
|
for v in v { s += v }
|
||||||
|
s
|
||||||
}
|
}
|
||||||
f()
|
test(f(), 6)
|
||||||
test(s, 6)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -55,30 +55,28 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test that the expression is evaluated to the end.
|
// Test that the expression is evaluated to the end.
|
||||||
#let y = 1
|
#let sum(..args) = {
|
||||||
#let identity(x, ..rest) = x
|
let s = 0
|
||||||
#let f(x) = {
|
for v in args.positional() {
|
||||||
identity(
|
s += v
|
||||||
..return,
|
}
|
||||||
x + 1,
|
s
|
||||||
y = 2,
|
}
|
||||||
)
|
|
||||||
|
#let f() = {
|
||||||
|
sum(..return, 1, 2, 3)
|
||||||
"nope"
|
"nope"
|
||||||
}
|
}
|
||||||
|
|
||||||
#test(f(1), 2)
|
#test(f(), 6)
|
||||||
#test(y, 2)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test value return from content.
|
// Test value return from content.
|
||||||
#let x = 3
|
#let x = 3
|
||||||
#let f() = [
|
#let f() = [
|
||||||
Hello 😀
|
Hello 😀
|
||||||
{ x = 1 }
|
|
||||||
#return "nope"
|
#return "nope"
|
||||||
{ x = 2 }
|
|
||||||
World
|
World
|
||||||
]
|
]
|
||||||
|
|
||||||
#test(f(), "nope")
|
#test(f(), "nope")
|
||||||
#test(x, 1)
|
|
||||||
|
@ -23,16 +23,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test storing arguments in a variable.
|
// Test doing things with arguments.
|
||||||
{
|
{
|
||||||
let args
|
let save(..args) = {
|
||||||
let save(..sink) = {
|
test(type(args), "arguments")
|
||||||
args = sink
|
test(repr(args), "(1, 2, three: true)")
|
||||||
}
|
}
|
||||||
|
|
||||||
save(1, 2, three: true)
|
save(1, 2, three: true)
|
||||||
test(type(args), "arguments")
|
|
||||||
test(repr(args), "(1, 2, three: true)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -7,13 +7,14 @@
|
|||||||
30pt, 50%, 20pt, 100%,
|
30pt, 50%, 20pt, 100%,
|
||||||
)
|
)
|
||||||
|
|
||||||
#let shaded = {
|
#let shaded(i, w) = {
|
||||||
let v = 0%
|
let v = (i + 1) * 10%
|
||||||
let next() = { v += 10%; rgb(v, v, v) }
|
rect(width: w, height: 10pt, fill: rgb(v, v, v))
|
||||||
w => rect(width: w, height: 10pt, fill: next())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#let items = for w in widths { (align(right, shaded(w)),) }
|
#let items = for i, w in widths {
|
||||||
|
(align(right, shaded(i, w)),)
|
||||||
|
}
|
||||||
|
|
||||||
#set page(width: 50pt, margins: 0pt)
|
#set page(width: 50pt, margins: 0pt)
|
||||||
#stack(dir: btt, ..items)
|
#stack(dir: btt, ..items)
|
||||||
|
@ -70,10 +70,10 @@ fn main() {
|
|||||||
);
|
);
|
||||||
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
|
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
|
||||||
|
|
||||||
// Hook up an assert function into the global scope.
|
// Hook up two more colors and an assert function into the global scope.
|
||||||
let mut std = typst::library::new();
|
let mut std = typst::library::new();
|
||||||
std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
||||||
std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
||||||
std.def_fn("test", move |_, args| {
|
std.def_fn("test", move |_, args| {
|
||||||
let lhs = args.expect::<Value>("left-hand side")?;
|
let lhs = args.expect::<Value>("left-hand side")?;
|
||||||
let rhs = args.expect::<Value>("right-hand side")?;
|
let rhs = args.expect::<Value>("right-hand side")?;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user