summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-23 15:03:10 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-23 15:23:52 +0100
commit4653ffebb43d733a3cff873d0903c7d00aaeb499 (patch)
tree6a97b2e6a6903b3198547d6f3d0a7e3d2eb023cd /src
parent84c6c8b0e6b17996a603ec88b7490107154f38f3 (diff)
Math module
Diffstat (limited to 'src')
-rw-r--r--src/ide/complete.rs7
-rw-r--r--src/ide/tooltip.rs14
-rw-r--r--src/model/eval.rs25
-rw-r--r--src/model/library.rs10
-rw-r--r--src/model/module.rs22
-rw-r--r--src/model/scope.rs44
-rw-r--r--src/syntax/ast.rs2
-rw-r--r--src/syntax/parser.rs7
8 files changed, 88 insertions, 43 deletions
diff --git a/src/ide/complete.rs b/src/ide/complete.rs
index f0808b21..5b044746 100644
--- a/src/ide/complete.rs
+++ b/src/ide/complete.rs
@@ -4,7 +4,6 @@ use if_chain::if_chain;
use super::{plain_docs_sentence, summarize_font_family};
use crate::model::{CastInfo, Scope, Value};
-use crate::syntax::ast::AstNode;
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
use crate::util::{format_eco, EcoString};
use crate::World;
@@ -118,8 +117,8 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
if let Some(grand) = parent.parent();
if let Some(expr) = grand.cast::<ast::Expr>();
let set = matches!(expr, ast::Expr::Set(_));
- if let Some(callee) = match expr {
- ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(),
+ if let Some(ast::Expr::Ident(callee)) = match expr {
+ ast::Expr::FuncCall(call) => Some(call.callee()),
ast::Expr::Set(set) => Some(set.target()),
_ => None,
};
@@ -377,7 +376,7 @@ impl<'a> CompletionContext<'a> {
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
Some(Self {
world,
- scope: &world.library().scope,
+ scope: &world.library().global.scope(),
before: &text[..cursor],
after: &text[cursor..],
leaf,
diff --git a/src/ide/tooltip.rs b/src/ide/tooltip.rs
index 0d46695b..076e2b45 100644
--- a/src/ide/tooltip.rs
+++ b/src/ide/tooltip.rs
@@ -2,7 +2,7 @@ use if_chain::if_chain;
use super::{plain_docs_sentence, summarize_font_family};
use crate::model::{CastInfo, Value};
-use crate::syntax::ast::{self, AstNode};
+use crate::syntax::ast;
use crate::syntax::{LinkedNode, Source, SyntaxKind};
use crate::World;
@@ -23,7 +23,7 @@ fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
leaf.parent_kind(),
Some(SyntaxKind::FuncCall | SyntaxKind::SetRule),
);
- if let Some(Value::Func(func)) = world.library().scope.get(&ident);
+ if let Some(Value::Func(func)) = world.library().global.scope().get(&ident);
if let Some(info) = func.info();
then {
return Some(plain_docs_sentence(info.docs));
@@ -44,14 +44,14 @@ fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
if matches!(grand.kind(), SyntaxKind::Args);
if let Some(grand_grand) = grand.parent();
if let Some(expr) = grand_grand.cast::<ast::Expr>();
- if let Some(callee) = match expr {
- ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(),
+ if let Some(ast::Expr::Ident(callee)) = match expr {
+ ast::Expr::FuncCall(call) => Some(call.callee()),
ast::Expr::Set(set) => Some(set.target()),
_ => None,
};
// Find metadata about the function.
- if let Some(Value::Func(func)) = world.library().scope.get(&callee);
+ if let Some(Value::Func(func)) = world.library().global.scope().get(&callee);
if let Some(info) = func.info();
then { (info, named) }
else { return None; }
@@ -103,8 +103,8 @@ fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
if matches!(parent.kind(), SyntaxKind::Args);
if let Some(grand) = parent.parent();
if let Some(expr) = grand.cast::<ast::Expr>();
- if let Some(callee) = match expr {
- ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(),
+ if let Some(ast::Expr::Ident(callee)) = match expr {
+ ast::Expr::FuncCall(call) => Some(call.callee()),
ast::Expr::Set(set) => Some(set.target()),
_ => None,
};
diff --git a/src/model/eval.rs b/src/model/eval.rs
index 67c733ce..6de328bc 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -2,7 +2,7 @@
use std::collections::BTreeMap;
use std::mem;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use comemo::{Track, Tracked};
use unicode_segmentation::UnicodeSegmentation;
@@ -32,9 +32,9 @@ pub fn eval(
) -> SourceResult<Module> {
// Prevent cyclic evaluation.
let id = source.id();
+ let path = if id.is_detached() { Path::new("") } else { world.source(id).path() };
if route.contains(id) {
- let path = world.source(id).path().display();
- panic!("Tried to cyclicly evaluate {}", path);
+ panic!("Tried to cyclicly evaluate {}", path.display());
}
// Hook up the lang items.
@@ -43,7 +43,7 @@ pub fn eval(
// Evaluate the module.
let route = unsafe { Route::insert(route, id) };
- let scopes = Scopes::new(Some(&library.scope));
+ let scopes = Scopes::new(Some(library));
let mut vm = Vm::new(world, route.track(), id, scopes, 0);
let result = source.ast()?.eval(&mut vm);
@@ -53,7 +53,8 @@ pub fn eval(
}
// Assemble the module.
- Ok(Module::evaluated(source.path(), vm.scopes.top, result?))
+ let name = path.file_stem().unwrap_or_default().to_string_lossy();
+ Ok(Module::new(name).with_scope(vm.scopes.top).with_content(result?))
}
/// A virtual machine.
@@ -521,7 +522,7 @@ impl Eval for ast::Math {
.map(|expr| expr.eval_in_math(vm))
.collect::<SourceResult<_>>()?;
let block = self.block();
- Ok((vm.items.math)(Content::sequence(seq), block))
+ Ok((vm.items.math_formula)(Content::sequence(seq), block))
}
}
@@ -608,11 +609,11 @@ impl Eval for ast::Ident {
impl ast::Ident {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
if self.as_untyped().len() == self.len()
- && matches!(vm.scopes.get(self), Ok(Value::Func(_)) | Err(_))
+ && matches!(vm.scopes.get_in_math(self), Ok(Value::Func(_)) | Err(_))
{
Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into()))
} else {
- Ok(self.eval(vm)?.display_in_math())
+ Ok(vm.scopes.get_in_math(self).at(self.span())?.clone().display_in_math())
}
}
}
@@ -933,7 +934,13 @@ impl Eval for ast::FuncCall {
impl ast::FuncCall {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
- let callee = self.callee().eval(vm)?;
+ let callee = match self.callee() {
+ ast::Expr::Ident(ident) => {
+ vm.scopes.get_in_math(&ident).at(ident.span())?.clone()
+ }
+ expr => expr.eval(vm)?,
+ };
+
if let Value::Func(callee) = callee {
let args = self.args().eval(vm)?;
Ok(Self::eval_call(vm, &callee, args, self.span())?.display_in_math())
diff --git a/src/model/library.rs b/src/model/library.rs
index 54eeeb5b..a64b0263 100644
--- a/src/model/library.rs
+++ b/src/model/library.rs
@@ -4,7 +4,7 @@ use std::num::NonZeroUsize;
use once_cell::sync::OnceCell;
-use super::{Content, NodeId, Scope, StyleChain, StyleMap, Vt};
+use super::{Content, Module, NodeId, StyleChain, StyleMap, Vt};
use crate::diag::SourceResult;
use crate::doc::Document;
use crate::geom::{Abs, Dir};
@@ -14,7 +14,9 @@ use crate::util::{hash128, EcoString};
#[derive(Debug, Clone, Hash)]
pub struct Library {
/// The scope containing definitions that are available everywhere.
- pub scope: Scope,
+ pub global: Module,
+ /// The scope containing definitions available in math mode.
+ pub math: Module,
/// The default properties for page size, font selection and so on.
pub styles: StyleMap,
/// Defines which standard library items fulfill which syntactical roles.
@@ -66,7 +68,7 @@ pub struct LangItems {
/// An item in a term list: `/ Term: Details`.
pub term_item: fn(term: Content, description: Content) -> Content,
/// A mathematical formula: `$x$`, `$ x^2 $`.
- pub math: fn(body: Content, block: bool) -> Content,
+ pub math_formula: fn(body: Content, block: bool) -> Content,
/// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`.
pub math_delimited: fn(body: Content) -> Content,
@@ -106,7 +108,7 @@ impl Hash for LangItems {
self.list_item.hash(state);
self.enum_item.hash(state);
self.term_item.hash(state);
- self.math.hash(state);
+ self.math_formula.hash(state);
self.math_atom.hash(state);
self.math_script.hash(state);
self.math_frac.hash(state);
diff --git a/src/model/module.rs b/src/model/module.rs
index ba6c76fb..6a1c60a5 100644
--- a/src/model/module.rs
+++ b/src/model/module.rs
@@ -1,5 +1,4 @@
use std::fmt::{self, Debug, Formatter};
-use std::path::Path;
use std::sync::Arc;
use super::{Content, Scope, Value};
@@ -22,7 +21,7 @@ struct Repr {
}
impl Module {
- /// Create a new, empty module with the given `name`.
+ /// Create a new module.
pub fn new(name: impl Into<EcoString>) -> Self {
Self(Arc::new(Repr {
name: name.into(),
@@ -31,10 +30,16 @@ impl Module {
}))
}
- /// Create a new module from an evalauted file.
- pub fn evaluated(path: &Path, scope: Scope, content: Content) -> Self {
- let name = path.file_stem().unwrap_or_default().to_string_lossy().into();
- Self(Arc::new(Repr { name, scope, content }))
+ /// Update the module's scope.
+ pub fn with_scope(mut self, scope: Scope) -> Self {
+ Arc::make_mut(&mut self.0).scope = scope;
+ self
+ }
+
+ /// Update the module's content.
+ pub fn with_content(mut self, content: Content) -> Self {
+ Arc::make_mut(&mut self.0).content = content;
+ self
}
/// Get the module's name.
@@ -47,6 +52,11 @@ impl Module {
&self.0.scope
}
+ /// Access the module's scope, mutably.
+ pub fn scope_mut(&mut self) -> &mut Scope {
+ &mut Arc::make_mut(&mut 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(|| {
diff --git a/src/model/scope.rs b/src/model/scope.rs
index c54cf1b3..bb0d4684 100644
--- a/src/model/scope.rs
+++ b/src/model/scope.rs
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
-use super::{Func, FuncType, Value};
+use super::{Func, FuncType, Library, Value};
use crate::diag::StrResult;
use crate::util::EcoString;
@@ -13,13 +13,13 @@ pub struct Scopes<'a> {
pub top: Scope,
/// The stack of lower scopes.
pub scopes: Vec<Scope>,
- /// The base scope.
- pub base: Option<&'a Scope>,
+ /// The standard library.
+ pub base: Option<&'a Library>,
}
impl<'a> Scopes<'a> {
/// Create a new, empty hierarchy of scopes.
- pub fn new(base: Option<&'a Scope>) -> Self {
+ pub fn new(base: Option<&'a Library>) -> Self {
Self { top: Scope::new(), scopes: vec![], base }
}
@@ -39,7 +39,16 @@ impl<'a> Scopes<'a> {
pub fn get(&self, var: &str) -> StrResult<&Value> {
Ok(std::iter::once(&self.top)
.chain(self.scopes.iter().rev())
- .chain(self.base.into_iter())
+ .chain(self.base.map(|base| base.global.scope()))
+ .find_map(|scope| scope.get(var))
+ .ok_or("unknown variable")?)
+ }
+
+ /// Try to access a variable immutably from within a math formula.
+ pub fn get_in_math(&self, var: &str) -> StrResult<&Value> {
+ Ok(std::iter::once(&self.top)
+ .chain(self.scopes.iter().rev())
+ .chain(self.base.map(|base| base.math.scope()))
.find_map(|scope| scope.get(var))
.ok_or("unknown variable")?)
}
@@ -50,10 +59,9 @@ impl<'a> Scopes<'a> {
.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"
+ match self.base.and_then(|base| base.global.scope().get(var)) {
+ Some(_) => "cannot mutate a constant",
+ _ => "unknown variable",
}
})?
}
@@ -61,17 +69,29 @@ impl<'a> Scopes<'a> {
/// A map from binding names to values.
#[derive(Default, Clone, Hash)]
-pub struct Scope(BTreeMap<EcoString, Slot>);
+pub struct Scope(BTreeMap<EcoString, Slot>, bool);
impl Scope {
/// Create a new empty scope.
pub fn new() -> Self {
- Self::default()
+ Self(BTreeMap::new(), false)
+ }
+
+ /// Create a new scope with duplication prevention.
+ pub fn deduplicating() -> Self {
+ Self(BTreeMap::new(), true)
}
/// Bind a value to a name.
pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) {
- self.0.insert(name.into(), Slot::new(value.into(), Kind::Normal));
+ let name = name.into();
+
+ #[cfg(debug_assertions)]
+ if self.1 && self.0.contains_key(&name) {
+ panic!("duplicate definition: {name}");
+ }
+
+ self.0.insert(name, Slot::new(value.into(), Kind::Normal));
}
/// Define a function through a native rust function.
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index ceda2d57..4bab0c42 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -1505,7 +1505,7 @@ node! {
impl SetRule {
/// The function to set style properties for.
- pub fn target(&self) -> Ident {
+ pub fn target(&self) -> Expr {
self.0.cast_first_match().expect("set rule is missing target")
}
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index 15839e18..07f53372 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -712,7 +712,14 @@ fn let_binding(p: &mut Parser) {
fn set_rule(p: &mut Parser) {
let m = p.marker();
p.assert(SyntaxKind::Set);
+
+ let m2 = p.marker();
p.expect(SyntaxKind::Ident);
+ while p.eat_if(SyntaxKind::Dot) {
+ p.expect(SyntaxKind::Ident);
+ p.wrap(m2, SyntaxKind::FieldAccess);
+ }
+
args(p);
if p.eat_if(SyntaxKind::If) {
code_expr(p);