From f88ef45ee6e285df59c7aa5cec935de331b4b6e0 Mon Sep 17 00:00:00 2001 From: Pg Biel <9021226+PgBiel@users.noreply.github.com> Date: Wed, 3 May 2023 09:20:53 -0300 Subject: [PATCH] Function scopes (#1032) --- library/src/compute/foundations.rs | 92 ++++++++++++++++++++++++ library/src/layout/enum.rs | 15 ++++ macros/src/element.rs | 28 +++----- macros/src/func.rs | 9 ++- macros/src/util.rs | 34 +++++++++ src/eval/func.rs | 29 +++++++- src/eval/mod.rs | 112 +++++++++++++++++++++-------- src/eval/value.rs | 1 + src/ide/complete.rs | 8 +++ tests/ref/compiler/import.png | Bin 4446 -> 5941 bytes tests/ref/layout/enum.png | Bin 16078 -> 18946 bytes tests/typ/compiler/field.typ | 24 +++++++ tests/typ/compiler/import.typ | 45 +++++++++++- tests/typ/compute/foundations.typ | 26 +++++++ tests/typ/layout/enum.typ | 12 ++++ 15 files changed, 383 insertions(+), 52 deletions(-) diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index bad9f8ab1..5127fca3d 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -88,6 +88,9 @@ pub fn panic( /// Fails with an error if the condition is not fulfilled. Does not /// produce any output in the document. /// +/// If you wish to test equality between two values, see +/// [`assert.eq`]($func/assert.eq) and [`assert.ne`]($func/assert.ne). +/// /// ## Example /// ```typ /// #assert(1 < 2, message: "math broke") @@ -97,6 +100,11 @@ pub fn panic( /// Category: foundations /// Returns: #[func] +#[scope( + scope.define("eq", assert_eq); + scope.define("ne", assert_ne); + scope +)] pub fn assert( /// The condition that must be true for the assertion to pass. condition: bool, @@ -115,6 +123,90 @@ pub fn assert( Value::None } +/// Ensure that two values are equal. +/// +/// Fails with an error if the first value is not equal to the second. Does not +/// produce any output in the document. +/// +/// ## Example +/// ```example +/// #assert.eq(10, 10) +/// ``` +/// +/// Display: Assert Equals +/// Category: foundations +/// Returns: +#[func] +pub fn assert_eq( + /// The first value to compare. + left: Value, + + /// The second value to compare. + right: Value, + + /// An optional message to display on error instead of the representations + /// of the compared values. + #[named] + #[default] + message: Option, +) -> Value { + if left != right { + if let Some(message) = message { + bail!(args.span, "equality assertion failed: {}", message); + } else { + bail!( + args.span, + "equality assertion failed: value {:?} was not equal to {:?}", + left, + right + ); + } + } + Value::None +} + +/// Ensure that two values are not equal. +/// +/// Fails with an error if the first value is equal to the second. Does not +/// produce any output in the document. +/// +/// ## Example +/// ```example +/// #assert.ne(3, 4) +/// ``` +/// +/// Display: Assert Not Equals +/// Category: foundations +/// Returns: +#[func] +pub fn assert_ne( + /// The first value to compare. + left: Value, + + /// The second value to compare. + right: Value, + + /// An optional message to display on error instead of the representations + /// of the compared values. + #[named] + #[default] + message: Option, +) -> Value { + if left == right { + if let Some(message) = message { + bail!(args.span, "inequality assertion failed: {}", message); + } else { + bail!( + args.span, + "inequality assertion failed: value {:?} was equal to {:?}", + left, + right + ); + } + } + Value::None +} + /// Evaluate a string as Typst code. /// /// This function should only be used as a last resort. diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index 8814aba38..a0b239456 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -36,6 +36,17 @@ use super::GridLayouter; /// + Don't forget step two /// ``` /// +/// You can also use [`enum.item`]($func/enum.item) to programmatically +/// customize the number of each item in the enumeration: +/// +/// ```example +/// #enum( +/// enum.item(1)[First step], +/// enum.item(5)[Fifth step], +/// enum.item(10)[Tenth step] +/// ) +/// ``` +/// /// ## Syntax /// This functions also has dedicated syntax: /// @@ -51,6 +62,10 @@ use super::GridLayouter; /// Display: Numbered List /// Category: layout #[element(Layout)] +#[scope( + scope.define("item", EnumItem::func()); + scope +)] pub struct EnumElem { /// If this is `{false}`, the items are spaced apart with /// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal diff --git a/macros/src/element.rs b/macros/src/element.rs index 37ca19eb7..403af103a 100644 --- a/macros/src/element.rs +++ b/macros/src/element.rs @@ -15,6 +15,7 @@ struct Elem { ident: Ident, capable: Vec, fields: Vec, + scope: Option, } struct Field { @@ -28,7 +29,7 @@ struct Field { synthesized: bool, fold: bool, resolve: bool, - parse: Option, + parse: Option, default: syn::Expr, vis: syn::Visibility, ident: Ident, @@ -50,21 +51,6 @@ impl Field { } } -struct FieldParser { - prefix: Vec, - expr: syn::Stmt, -} - -impl Parse for FieldParser { - fn parse(input: ParseStream) -> Result { - let mut stmts = syn::Block::parse_within(input)?; - let Some(expr) = stmts.pop() else { - return Err(input.error("expected at least on expression")); - }; - Ok(Self { prefix: stmts, expr }) - } -} - /// Preprocess the element's definition. fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { let syn::Fields::Named(named) = &body.fields else { @@ -137,7 +123,8 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { .into_iter() .collect(); - let docs = documentation(&body.attrs); + let mut attrs = body.attrs.clone(); + let docs = documentation(&attrs); let mut lines = docs.split('\n').collect(); let category = meta_line(&mut lines, "Category")?.into(); let display = meta_line(&mut lines, "Display")?.into(); @@ -152,9 +139,10 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { ident: body.ident.clone(), capable, fields, + scope: parse_attr(&mut attrs, "scope")?.flatten(), }; - validate_attrs(&body.attrs)?; + validate_attrs(&attrs)?; Ok(element) } @@ -351,6 +339,7 @@ fn create_pack_impl(element: &Elem) -> TokenStream { .iter() .filter(|field| !field.internal && !field.synthesized) .map(create_param_info); + let scope = create_scope_builder(element.scope.as_ref()); quote! { impl ::typst::model::Element for #ident { fn pack(self) -> ::typst::model::Content { @@ -377,6 +366,7 @@ fn create_pack_impl(element: &Elem) -> TokenStream { params: ::std::vec![#(#infos),*], returns: ::std::vec!["content"], category: #category, + scope: #scope, }), }; (&NATIVE).into() @@ -519,7 +509,7 @@ fn create_set_impl(element: &Elem) -> TokenStream { /// Create argument parsing code for a field. fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { - if let Some(FieldParser { prefix, expr }) = &field.parse { + if let Some(BlockWithReturn { prefix, expr }) = &field.parse { return (quote! { #(#prefix);* }, quote! { #expr }); } diff --git a/macros/src/func.rs b/macros/src/func.rs index 386ed7c42..f3de68229 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -18,6 +18,7 @@ struct Func { params: Vec, returns: Vec, body: syn::Block, + scope: Option, } struct Param { @@ -72,7 +73,8 @@ fn prepare(item: &syn::ItemFn) -> Result { validate_attrs(&attrs)?; } - let docs = documentation(&item.attrs); + let mut attrs = item.attrs.clone(); + let docs = documentation(&attrs); let mut lines = docs.split('\n').collect(); let returns = meta_line(&mut lines, "Returns")? .split(" or ") @@ -92,9 +94,10 @@ fn prepare(item: &syn::ItemFn) -> Result { params, returns, body: (*item.block).clone(), + scope: parse_attr(&mut attrs, "scope")?.flatten(), }; - validate_attrs(&item.attrs)?; + validate_attrs(&attrs)?; Ok(func) } @@ -113,6 +116,7 @@ fn create(func: &Func) -> TokenStream { } = func; let handlers = params.iter().filter(|param| !param.external).map(create_param_parser); let params = params.iter().map(create_param_info); + let scope = create_scope_builder(func.scope.as_ref()); quote! { #[doc = #docs] #vis fn #ident() -> &'static ::typst::eval::NativeFunc { @@ -129,6 +133,7 @@ fn create(func: &Func) -> TokenStream { params: ::std::vec![#(#params),*], returns: ::std::vec![#(#returns),*], category: #category, + scope: #scope, }), }; &FUNC diff --git a/macros/src/util.rs b/macros/src/util.rs index 53a8354e8..6b683e5d2 100644 --- a/macros/src/util.rs +++ b/macros/src/util.rs @@ -18,6 +18,27 @@ macro_rules! bail { }; } +/// For parsing attributes of the form: +/// #[attr( +/// statement; +/// statement; +/// returned_expression +/// )] +pub struct BlockWithReturn { + pub prefix: Vec, + pub expr: syn::Stmt, +} + +impl Parse for BlockWithReturn { + fn parse(input: ParseStream) -> Result { + let mut stmts = syn::Block::parse_within(input)?; + let Some(expr) = stmts.pop() else { + return Err(input.error("expected at least one expression")); + }; + Ok(Self { prefix: stmts, expr }) + } +} + /// Whether an attribute list has a specified attribute. pub fn has_attr(attrs: &mut Vec, target: &str) -> bool { take_attr(attrs, target).is_some() @@ -88,3 +109,16 @@ pub fn meta_line<'a>(lines: &mut Vec<&'a str>, key: &str) -> Result<&'a str> { None => bail!(callsite, "missing metadata key: {}", key), } } + +/// Creates a block responsible for building a Scope. +pub fn create_scope_builder(scope_block: Option<&BlockWithReturn>) -> TokenStream { + if let Some(BlockWithReturn { prefix, expr }) = scope_block { + quote! { { + let mut scope = ::typst::eval::Scope::deduplicating(); + #(#prefix);* + #expr + } } + } else { + quote! { ::typst::eval::Scope::new() } + } +} diff --git a/src/eval/func.rs b/src/eval/func.rs index a6e0de84d..51eba564e 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -5,12 +5,13 @@ use std::hash::{Hash, Hasher}; use std::sync::Arc; use comemo::{Prehashed, Track, Tracked, TrackedMut}; +use ecow::eco_format; use once_cell::sync::Lazy; use super::{ cast_to_value, Args, CastInfo, Eval, Flow, Route, Scope, Scopes, Tracer, Value, Vm, }; -use crate::diag::{bail, SourceResult}; +use crate::diag::{bail, SourceResult, StrResult}; use crate::model::{ElemFunc, Introspector, StabilityProvider, Vt}; use crate::syntax::ast::{self, AstNode, Expr, Ident}; use crate::syntax::{SourceId, Span, SyntaxNode}; @@ -144,6 +145,30 @@ impl Func { _ => None, } } + + /// Get a field from this function's scope, if possible. + pub fn get(&self, field: &str) -> StrResult<&Value> { + match &self.repr { + Repr::Native(func) => func.info.scope.get(field).ok_or_else(|| { + eco_format!( + "function `{}` does not contain field `{}`", + func.info.name, + field + ) + }), + Repr::Elem(func) => func.info().scope.get(field).ok_or_else(|| { + eco_format!( + "function `{}` does not contain field `{}`", + func.name(), + field + ) + }), + Repr::Closure(_) => { + Err(eco_format!("cannot access fields on user-defined functions")) + } + Repr::With(arc) => arc.0.get(field), + } + } } impl Debug for Func { @@ -225,6 +250,8 @@ pub struct FuncInfo { pub returns: Vec<&'static str>, /// Which category the function is part of. pub category: &'static str, + /// The function's own scope of fields and sub-functions. + pub scope: Scope, } impl FuncInfo { diff --git a/src/eval/mod.rs b/src/eval/mod.rs index b430b4003..a837c9e02 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -42,7 +42,7 @@ use std::mem; use std::path::{Path, PathBuf}; use comemo::{Track, Tracked, TrackedMut}; -use ecow::EcoVec; +use ecow::{EcoString, EcoVec}; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{ @@ -1077,7 +1077,15 @@ impl Eval for ast::FuncCall { if methods::is_mutating(&field) { let args = args.eval(vm)?; let target = target.access(vm)?; - if !matches!(target, Value::Symbol(_) | Value::Module(_)) { + + // Prioritize a function's own methods (with, where) over its + // fields. This is fine as we define each field of a function, + // if it has any. + // ('methods_on' will be empty for Symbol and Module - their + // method calls always refer to their fields.) + if !matches!(target, Value::Symbol(_) | Value::Module(_) | Value::Func(_)) + || methods_on(target.type_name()).iter().any(|(m, _)| m == &field) + { return methods::call_mut(target, &field, args, span).trace( vm.world(), point, @@ -1088,7 +1096,10 @@ impl Eval for ast::FuncCall { } else { let target = target.eval(vm)?; let args = args.eval(vm)?; - if !matches!(target, Value::Symbol(_) | Value::Module(_)) { + + if !matches!(target, Value::Symbol(_) | Value::Module(_) | Value::Func(_)) + || methods_on(target.type_name()).iter().any(|(m, _)| m == &field) + { return methods::call(vm, target, &field, args, span).trace( vm.world(), point, @@ -1613,6 +1624,42 @@ impl Eval for ast::ForLoop { } } +/// Applies imports from `import` to the current scope. +fn apply_imports>( + imports: Option, + vm: &mut Vm, + source_value: V, + name: impl Fn(&V) -> EcoString, + scope: impl Fn(&V) -> &Scope, +) -> SourceResult<()> { + match imports { + None => { + vm.scopes.top.define(name(&source_value), source_value); + } + Some(ast::Imports::Wildcard) => { + for (var, value) in scope(&source_value).iter() { + vm.scopes.top.define(var.clone(), value.clone()); + } + } + Some(ast::Imports::Items(idents)) => { + let mut errors = vec![]; + let scope = scope(&source_value); + for ident in idents { + if let Some(value) = scope.get(&ident) { + vm.define(ident, value.clone()); + } else { + errors.push(error!(ident.span(), "unresolved import")); + } + } + if !errors.is_empty() { + return Err(Box::new(errors)); + } + } + } + + Ok(()) +} + impl Eval for ast::ModuleImport { type Output = Value; @@ -1620,30 +1667,26 @@ impl Eval for ast::ModuleImport { fn eval(&self, vm: &mut Vm) -> SourceResult { let span = self.source().span(); let source = self.source().eval(vm)?; - let module = import(vm, source, span)?; - - match self.imports() { - None => { - vm.scopes.top.define(module.name().clone(), module); - } - Some(ast::Imports::Wildcard) => { - for (var, value) in module.scope().iter() { - vm.scopes.top.define(var.clone(), value.clone()); - } - } - Some(ast::Imports::Items(idents)) => { - let mut errors = vec![]; - for ident in idents { - if let Some(value) = module.scope().get(&ident) { - vm.define(ident, value.clone()); - } else { - errors.push(error!(ident.span(), "unresolved import")); - } - } - if !errors.is_empty() { - return Err(Box::new(errors)); - } + if let Value::Func(func) = source { + if func.info().is_none() { + bail!(span, "cannot import from user-defined functions"); } + apply_imports( + self.imports(), + vm, + func, + |func| func.info().unwrap().name.into(), + |func| &func.info().unwrap().scope, + )?; + } else { + let module = import(vm, source, span, true)?; + apply_imports( + self.imports(), + vm, + module, + |module| module.name().clone(), + |module| module.scope(), + )?; } Ok(Value::None) @@ -1657,17 +1700,28 @@ impl Eval for ast::ModuleInclude { fn eval(&self, vm: &mut Vm) -> SourceResult { let span = self.source().span(); let source = self.source().eval(vm)?; - let module = import(vm, source, span)?; + let module = import(vm, source, span, false)?; Ok(module.content()) } } /// Process an import of a module relative to the current location. -fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult { +fn import( + vm: &mut Vm, + source: Value, + span: Span, + accept_functions: bool, +) -> SourceResult { let path = match source { Value::Str(path) => path, Value::Module(module) => return Ok(module), - v => bail!(span, "expected path or module, found {}", v.type_name()), + v => { + if accept_functions { + bail!(span, "expected path, module or function, found {}", v.type_name()) + } else { + bail!(span, "expected path or module, found {}", v.type_name()) + } + } }; // Load the source file. diff --git a/src/eval/value.rs b/src/eval/value.rs index 1bfad9c87..bd612cce6 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -127,6 +127,7 @@ impl Value { Self::Dict(dict) => dict.at(field, None).cloned(), Self::Content(content) => content.at(field, None), Self::Module(module) => module.get(field).cloned(), + Self::Func(func) => func.get(field).cloned(), v => Err(eco_format!("cannot access fields on type {}", v.type_name())), } } diff --git a/src/ide/complete.rs b/src/ide/complete.rs index e20229a60..f9f19bd69 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -388,6 +388,14 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { ctx.value_completion(Some(name.clone()), value, true, None); } } + Value::Func(func) => { + if let Some(info) = func.info() { + // Consider all names from the function's scope. + for (name, value) in info.scope.iter() { + ctx.value_completion(Some(name.clone()), value, true, None); + } + } + } _ => {} } } diff --git a/tests/ref/compiler/import.png b/tests/ref/compiler/import.png index bf95f45df4839e19078e5670b816a5adff970093..5c6132d2f7a55bf1c2a5fe45791d4b7a9bee7f3e 100644 GIT binary patch literal 5941 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 literal 4446 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 diff --git a/tests/ref/layout/enum.png b/tests/ref/layout/enum.png index fb2c2a636b59b6900054a4b236ebfd7825c40941..94a9ed51acc04712f029b3c08d7eaf54b4078bc9 100644 GIT binary patch delta 13529 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 diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ index d1e4a31a0..dd8499cef 100644 --- a/tests/typ/compiler/field.typ +++ b/tests/typ/compiler/field.typ @@ -22,6 +22,30 @@ - B - C +--- +// Test fields on function scopes. +#enum.item +#assert.eq +#assert.ne + +--- +// Error: 9-16 function `assert` does not contain field `invalid` +#assert.invalid + +--- +// Error: 7-14 function `enum` does not contain field `invalid` +#enum.invalid + +--- +// Error: 7-14 function `enum` does not contain field `invalid` +#enum.invalid() + +--- +// Closures cannot have fields. +#let f(x) = x +// Error: 4-11 cannot access fields on user-defined functions +#f.invalid + --- // Error: 6-13 dictionary does not contain key "invalid" and no default value was specified #(:).invalid diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ index 68b6af2e1..0f1617668 100644 --- a/tests/typ/compiler/import.typ +++ b/tests/typ/compiler/import.typ @@ -1,4 +1,4 @@ -// Test module imports. +// Test function and module imports. // Ref: false --- @@ -34,6 +34,20 @@ // It exists now! #test(d, 3) +--- +// Test importing from function scopes. +// Ref: true + +#import enum: item +#import assert.with(true): * + +#enum( + item(1)[First], + item(5)[Fifth] +) +#eq(10, 10) +#ne(5, 6) + --- // A module import without items. #import "module.typ" @@ -59,6 +73,35 @@ // Allow the trailing comma. #import "module.typ": a, c, +--- +// Usual importing syntax also works for function scopes +#import enum +#let d = (e: enum) +#import d.e +#import d.e: item + +#item(2)[a] + +--- +// Can't import from closures. +#let f(x) = x +// Error: 9-10 cannot import from user-defined functions +#import f: x + +--- +// Can't import from closures, despite modifiers. +#let f(x) = x +// Error: 9-18 cannot import from user-defined functions +#import f.with(5): x + +--- +// Error: 9-18 cannot import from user-defined functions +#import () => {5}: x + +--- +// Error: 9-10 expected path, module or function, found integer +#import 5: something + --- // Error: 9-11 failed to load file (is a directory) #import "": name diff --git a/tests/typ/compute/foundations.typ b/tests/typ/compute/foundations.typ index c74a4cd6b..9c7b13cab 100644 --- a/tests/typ/compute/foundations.typ +++ b/tests/typ/compute/foundations.typ @@ -40,6 +40,32 @@ // Error: 9-15 expected boolean, found string #assert("true") +--- +// Test failing assertions. +// Error: 11-19 equality assertion failed: value 10 was not equal to 11 +#assert.eq(10, 11) + +--- +// Test failing assertions. +// Error: 11-55 equality assertion failed: 10 and 12 are not equal +#assert.eq(10, 12, message: "10 and 12 are not equal") + +--- +// Test failing assertions. +// Error: 11-19 inequality assertion failed: value 11 was equal to 11 +#assert.ne(11, 11) + +--- +// Test failing assertions. +// Error: 11-57 inequality assertion failed: must be different from 11 +#assert.ne(11, 11, message: "must be different from 11") + +--- +// Test successful assertions. +#assert(5 > 3) +#assert.eq(15, 15) +#assert.ne(10, 12) + --- // Test the `type` function. #test(type(1), "integer") diff --git a/tests/typ/layout/enum.typ b/tests/typ/layout/enum.typ index 2606a64db..a90e18961 100644 --- a/tests/typ/layout/enum.typ +++ b/tests/typ/layout/enum.typ @@ -35,6 +35,18 @@ Empty \ +Nope \ a + 0. +--- +// Test item number overriding. +1. first ++ second +5. fifth + +#enum( + enum.item(1)[First], + [Second], + enum.item(5)[Fifth] +) + --- // Alignment shouldn't affect number #set align(horizon)