From 7fcad452b87c8bd31a9b7dfba78c1b1a92d33dd9 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 1 Oct 2020 15:03:37 +0200 Subject: [PATCH] =?UTF-8?q?Reorganize=20ast=20types=20=F0=9F=8F=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compute/dict.rs | 68 ++++++------ src/compute/value.rs | 10 +- src/layout/mod.rs | 4 +- src/layout/tree.rs | 43 ++++---- src/lib.rs | 6 +- src/library/align.rs | 2 +- src/library/boxed.rs | 2 +- src/library/font.rs | 2 +- src/parse/mod.rs | 148 +++++++++++++------------ src/parse/resolve.rs | 8 +- src/parse/tests.rs | 160 ++++++++++++++++----------- src/syntax/expr.rs | 124 +++++++++++++++++++++ src/syntax/ident.rs | 58 ++++++++++ src/syntax/lit.rs | 99 +++++++++++++++++ src/syntax/mod.rs | 14 ++- src/syntax/span.rs | 4 +- src/syntax/tree.rs | 249 +++---------------------------------------- 17 files changed, 554 insertions(+), 447 deletions(-) create mode 100644 src/syntax/expr.rs create mode 100644 src/syntax/ident.rs create mode 100644 src/syntax/lit.rs diff --git a/src/compute/dict.rs b/src/compute/dict.rs index e4df0048c..e62165724 100644 --- a/src/compute/dict.rs +++ b/src/compute/dict.rs @@ -52,38 +52,38 @@ impl Dict { /// Get a reference to the value with the given key. pub fn get<'a, K>(&self, key: K) -> Option<&V> where - K: Into>, + K: Into>, { match key.into() { - BorrowedKey::Num(num) => self.nums.get(&num), - BorrowedKey::Str(string) => self.strs.get(string), + RefKey::Num(num) => self.nums.get(&num), + RefKey::Str(string) => self.strs.get(string), } } /// Borrow the value with the given key mutably. pub fn get_mut<'a, K>(&mut self, key: K) -> Option<&mut V> where - K: Into>, + K: Into>, { match key.into() { - BorrowedKey::Num(num) => self.nums.get_mut(&num), - BorrowedKey::Str(string) => self.strs.get_mut(string), + RefKey::Num(num) => self.nums.get_mut(&num), + RefKey::Str(string) => self.strs.get_mut(string), } } /// Insert a value into the dictionary. pub fn insert(&mut self, key: K, value: V) where - K: Into, + K: Into, { match key.into() { - OwnedKey::Num(num) => { + DictKey::Num(num) => { self.nums.insert(num, value); if self.lowest_free == num { self.lowest_free += 1; } } - OwnedKey::Str(string) => { + DictKey::Str(string) => { self.strs.insert(string, value); } } @@ -92,14 +92,14 @@ impl Dict { /// Remove the value with the given key from the dictionary. pub fn remove<'a, K>(&mut self, key: K) -> Option where - K: Into>, + K: Into>, { match key.into() { - BorrowedKey::Num(num) => { + RefKey::Num(num) => { self.lowest_free = self.lowest_free.min(num); self.nums.remove(&num) } - BorrowedKey::Str(string) => self.strs.remove(string), + RefKey::Str(string) => self.strs.remove(string), } } @@ -116,10 +116,10 @@ impl Dict { } /// Iterator over all borrowed keys and values. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.nums() - .map(|(&k, v)| (BorrowedKey::Num(k), v)) - .chain(self.strs().map(|(k, v)| (BorrowedKey::Str(k), v))) + .map(|(&k, v)| (RefKey::Num(k), v)) + .chain(self.strs().map(|(k, v)| (RefKey::Str(k), v))) } /// Iterate over all values in the dictionary. @@ -138,11 +138,11 @@ impl Dict { } /// Move into an owned iterator over owned keys and values. - pub fn into_iter(self) -> impl Iterator { + pub fn into_iter(self) -> impl Iterator { self.nums .into_iter() - .map(|(k, v)| (OwnedKey::Num(k), v)) - .chain(self.strs.into_iter().map(|(k, v)| (OwnedKey::Str(k), v))) + .map(|(k, v)| (DictKey::Num(k), v)) + .chain(self.strs.into_iter().map(|(k, v)| (DictKey::Str(k), v))) } /// Move into an owned iterator over all values in the dictionary. @@ -166,7 +166,7 @@ impl Dict { impl<'a, K, V> Index for Dict where - K: Into>, + K: Into>, { type Output = V; @@ -230,33 +230,39 @@ impl Debug for Dict { /// The owned variant of a dictionary key. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum OwnedKey { +pub enum DictKey { Num(u64), Str(String), } -impl From> for OwnedKey { - fn from(key: BorrowedKey<'_>) -> Self { +impl From<&Self> for DictKey { + fn from(key: &Self) -> Self { + key.clone() + } +} + +impl From> for DictKey { + fn from(key: RefKey<'_>) -> Self { match key { - BorrowedKey::Num(num) => Self::Num(num), - BorrowedKey::Str(string) => Self::Str(string.to_string()), + RefKey::Num(num) => Self::Num(num), + RefKey::Str(string) => Self::Str(string.to_string()), } } } -impl From for OwnedKey { +impl From for DictKey { fn from(num: u64) -> Self { Self::Num(num) } } -impl From for OwnedKey { +impl From for DictKey { fn from(string: String) -> Self { Self::Str(string) } } -impl From<&'static str> for OwnedKey { +impl From<&'static str> for DictKey { fn from(string: &'static str) -> Self { Self::Str(string.to_string()) } @@ -264,24 +270,24 @@ impl From<&'static str> for OwnedKey { /// The borrowed variant of a dictionary key. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum BorrowedKey<'a> { +pub enum RefKey<'a> { Num(u64), Str(&'a str), } -impl From for BorrowedKey<'static> { +impl From for RefKey<'static> { fn from(num: u64) -> Self { Self::Num(num) } } -impl<'a> From<&'a String> for BorrowedKey<'a> { +impl<'a> From<&'a String> for RefKey<'a> { fn from(string: &'a String) -> Self { Self::Str(&string) } } -impl<'a> From<&'a str> for BorrowedKey<'a> { +impl<'a> From<&'a str> for RefKey<'a> { fn from(string: &'a str) -> Self { Self::Str(string) } diff --git a/src/compute/value.rs b/src/compute/value.rs index 1fcc4be21..cfbc302c2 100644 --- a/src/compute/value.rs +++ b/src/compute/value.rs @@ -11,7 +11,7 @@ use crate::color::RgbaColor; use crate::layout::{Command, Commands, Dir, LayoutContext, SpecAlign}; use crate::length::{Length, ScaleLength}; use crate::paper::Paper; -use crate::syntax::{Ident, Span, SpanWith, Spanned, SyntaxNode, SyntaxTree}; +use crate::syntax::{Ident, Span, SpanWith, Spanned, SynNode, SynTree}; use crate::{DynFuture, Feedback, Pass}; /// A computational value. @@ -32,7 +32,7 @@ pub enum Value { /// A dictionary value: `(false, 12cm, greeting="hi")`. Dict(DictValue), /// A syntax tree containing typesetting content. - Tree(SyntaxTree), + Tree(SynTree), /// An executable function. Func(FuncValue), /// Layouting commands. @@ -76,7 +76,7 @@ impl Spanned { for entry in dict.into_values() { if let Some(last_end) = end { let span = Span::new(last_end, entry.key.start); - let tree = vec![SyntaxNode::Spacing.span_with(span)]; + let tree = vec![SynNode::Spacing.span_with(span)]; commands.push(Command::LayoutSyntaxTree(tree)); } @@ -89,7 +89,7 @@ impl Spanned { // Format with debug. val => { let fmt = format!("{:?}", val); - let tree = vec![SyntaxNode::Text(fmt).span_with(self.span)]; + let tree = vec![SynNode::Text(fmt).span_with(self.span)]; vec![Command::LayoutSyntaxTree(tree)] } } @@ -340,7 +340,7 @@ impl_match!(String, "string", Value::Str(s) => s.clone()); impl_match!(bool, "bool", &Value::Bool(b) => b); impl_match!(f64, "number", &Value::Number(n) => n); impl_match!(Length, "length", &Value::Length(l) => l); -impl_match!(SyntaxTree, "tree", Value::Tree(t) => t.clone()); +impl_match!(SynTree, "tree", Value::Tree(t) => t.clone()); impl_match!(DictValue, "dict", Value::Dict(t) => t.clone()); impl_match!(FuncValue, "function", Value::Func(f) => f.clone()); impl_match!(ScaleLength, "number or length", diff --git a/src/layout/mod.rs b/src/layout/mod.rs index c053cda0b..768ff160f 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -25,7 +25,7 @@ use crate::compute::scope::Scope; use crate::font::SharedFontLoader; use crate::geom::{Margins, Size}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; -use crate::syntax::SyntaxTree; +use crate::syntax::SynTree; use elements::LayoutElements; use prelude::*; @@ -121,7 +121,7 @@ pub enum Command { /// This has the effect that the content fits nicely into the active line /// layouting, enabling functions to e.g. change the style of some piece of /// text while keeping it part of the current paragraph. - LayoutSyntaxTree(SyntaxTree), + LayoutSyntaxTree(SynTree), /// Add a finished layout. Add(BoxLayout), diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 24a003677..14ff4630e 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -5,12 +5,12 @@ use super::text::{layout_text, TextContext}; use super::*; use crate::style::LayoutStyle; use crate::syntax::{ - CallExpr, Decoration, Heading, Raw, Span, SpanWith, Spanned, SyntaxNode, SyntaxTree, + Decoration, Expr, NodeHeading, NodeRaw, Span, SpanWith, Spanned, SynNode, SynTree, }; use crate::{DynFuture, Feedback, Pass}; /// Layout a syntax tree into a collection of boxes. -pub async fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext<'_>) -> Pass { +pub async fn layout_tree(tree: &SynTree, ctx: LayoutContext<'_>) -> Pass { let mut layouter = TreeLayouter::new(ctx); layouter.layout_tree(tree).await; layouter.finish() @@ -44,7 +44,7 @@ impl<'a> TreeLayouter<'a> { Pass::new(self.layouter.finish(), self.feedback) } - fn layout_tree<'t>(&'t mut self, tree: &'t SyntaxTree) -> DynFuture<'t, ()> { + fn layout_tree<'t>(&'t mut self, tree: &'t SynTree) -> DynFuture<'t, ()> { Box::pin(async move { for node in tree { self.layout_node(node).await; @@ -52,26 +52,26 @@ impl<'a> TreeLayouter<'a> { }) } - async fn layout_node(&mut self, node: &Spanned) { + async fn layout_node(&mut self, node: &Spanned) { let decorate = |this: &mut Self, deco: Decoration| { this.feedback.decorations.push(deco.span_with(node.span)); }; match &node.v { - SyntaxNode::Spacing => self.layout_space(), - SyntaxNode::Linebreak => self.layouter.finish_line(), - SyntaxNode::Parbreak => self.layout_parbreak(), + SynNode::Spacing => self.layout_space(), + SynNode::Linebreak => self.layouter.finish_line(), + SynNode::Parbreak => self.layout_parbreak(), - SyntaxNode::ToggleItalic => { + SynNode::ToggleItalic => { self.style.text.italic = !self.style.text.italic; decorate(self, Decoration::Italic); } - SyntaxNode::ToggleBolder => { + SynNode::ToggleBolder => { self.style.text.bolder = !self.style.text.bolder; decorate(self, Decoration::Bold); } - SyntaxNode::Text(text) => { + SynNode::Text(text) => { if self.style.text.italic { decorate(self, Decoration::Italic); } @@ -81,12 +81,11 @@ impl<'a> TreeLayouter<'a> { self.layout_text(text).await; } - SyntaxNode::Heading(heading) => self.layout_heading(heading).await, + SynNode::Raw(raw) => self.layout_raw(raw).await, + SynNode::Heading(heading) => self.layout_heading(heading).await, - SyntaxNode::Raw(raw) => self.layout_raw(raw).await, - - SyntaxNode::Call(call) => { - self.layout_call(call.span_with(node.span)).await; + SynNode::Expr(expr) => { + self.layout_expr(expr.span_with(node.span)).await; } } } @@ -115,19 +114,19 @@ impl<'a> TreeLayouter<'a> { ); } - async fn layout_heading(&mut self, heading: &Heading) { + async fn layout_heading(&mut self, heading: &NodeHeading) { let style = self.style.text.clone(); self.style.text.font_scale *= 1.5 - 0.1 * heading.level.v.min(5) as f64; self.style.text.bolder = true; self.layout_parbreak(); - self.layout_tree(&heading.tree).await; + self.layout_tree(&heading.contents).await; self.layout_parbreak(); self.style.text = style; } - async fn layout_raw(&mut self, raw: &Raw) { + async fn layout_raw(&mut self, raw: &NodeRaw) { if !raw.inline { self.layout_parbreak(); } @@ -153,7 +152,7 @@ impl<'a> TreeLayouter<'a> { } } - async fn layout_call(&mut self, call: Spanned<&CallExpr>) { + async fn layout_expr(&mut self, expr: Spanned<&Expr>) { let ctx = LayoutContext { style: &self.style, spaces: self.layouter.remaining(), @@ -161,11 +160,11 @@ impl<'a> TreeLayouter<'a> { ..self.ctx }; - let val = call.v.eval(&ctx, &mut self.feedback).await; - let commands = val.span_with(call.span).into_commands(); + let val = expr.v.eval(&ctx, &mut self.feedback).await; + let commands = val.span_with(expr.span).into_commands(); for command in commands { - self.execute_command(command, call.span).await; + self.execute_command(command, expr.span).await; } } diff --git a/src/lib.rs b/src/lib.rs index 10e1f8108..f79acbdff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ use crate::diagnostic::Diagnostic; use crate::font::SharedFontLoader; use crate::layout::{Commands, MultiLayout}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; -use crate::syntax::{Decoration, Offset, Pos, SpanVec, SyntaxTree}; +use crate::syntax::{Decoration, Offset, Pos, SpanVec, SynTree}; /// Transforms source code into typesetted layouts. /// @@ -85,12 +85,12 @@ impl Typesetter { } /// Parse source code into a syntax tree. - pub fn parse(&self, src: &str) -> Pass { + pub fn parse(&self, src: &str) -> Pass { parse::parse(src) } /// Layout a syntax tree and return the produced layout. - pub async fn layout(&self, tree: &SyntaxTree) -> Pass { + pub async fn layout(&self, tree: &SynTree) -> Pass { use crate::layout::prelude::*; let margins = self.style.page.margins(); diff --git a/src/library/align.rs b/src/library/align.rs index 55259fd51..c909087a9 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -17,7 +17,7 @@ use super::*; pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass { let mut f = Feedback::new(); - let content = args.take::(); + let content = args.take::(); let h = args.take_key::>("horizontal", &mut f); let v = args.take_key::>("vertical", &mut f); let all = args diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 672e77d1e..e02b8c0c4 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -13,7 +13,7 @@ pub async fn boxed( ) -> Pass { let mut f = Feedback::new(); - let content = args.take::().unwrap_or(SyntaxTree::new()); + let content = args.take::().unwrap_or(SynTree::new()); ctx.base = ctx.spaces[0].size; ctx.spaces.truncate(1); diff --git a/src/library/font.rs b/src/library/font.rs index e9f6f9b4a..e01338223 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -28,7 +28,7 @@ pub async fn font(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass< let mut text = ctx.style.text.clone(); let mut updated_fallback = false; - let content = args.take::(); + let content = args.take::(); if let Some(s) = args.take::() { match s { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 75ca7eb40..7a0002d69 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -12,14 +12,13 @@ pub use tokens::*; use std::str::FromStr; -use super::*; use crate::color::RgbaColor; -use crate::compute::dict::SpannedEntry; +use crate::compute::dict::DictKey; use crate::syntax::*; use crate::{Feedback, Pass}; /// Parse a string of source code. -pub fn parse(src: &str) -> Pass { +pub fn parse(src: &str) -> Pass { Parser::new(src).parse() } @@ -42,7 +41,7 @@ impl<'s> Parser<'s> { } } - fn parse(mut self) -> Pass { + fn parse(mut self) -> Pass { let tree = self.parse_body_contents(); Pass::new(tree, self.feedback) } @@ -50,8 +49,8 @@ impl<'s> Parser<'s> { // Typesetting content. impl Parser<'_> { - fn parse_body_contents(&mut self) -> SyntaxTree { - let mut tree = SyntaxTree::new(); + fn parse_body_contents(&mut self) -> SynTree { + let mut tree = SynTree::new(); self.at_block_or_line_start = true; while !self.eof() { @@ -63,7 +62,7 @@ impl Parser<'_> { tree } - fn parse_node(&mut self) -> Option> { + fn parse_node(&mut self) -> Option> { let token = self.peek()?; let end = Span::at(token.span.end); @@ -83,11 +82,7 @@ impl Parser<'_> { self.at_block_or_line_start = true; } - self.with_span(if n >= 2 { - SyntaxNode::Parbreak - } else { - SyntaxNode::Spacing - }) + self.with_span(if n >= 2 { SynNode::Parbreak } else { SynNode::Spacing }) } Token::LineComment(_) | Token::BlockComment(_) => { @@ -99,15 +94,15 @@ impl Parser<'_> { Token::LeftBracket => { let call = self.parse_bracket_call(false); self.at_block_or_line_start = false; - call.map(SyntaxNode::Call) + call.map(|c| SynNode::Expr(Expr::Call(c))) } - Token::Star => self.with_span(SyntaxNode::ToggleBolder), - Token::Underscore => self.with_span(SyntaxNode::ToggleItalic), - Token::Backslash => self.with_span(SyntaxNode::Linebreak), + Token::Star => self.with_span(SynNode::ToggleBolder), + Token::Underscore => self.with_span(SynNode::ToggleItalic), + Token::Backslash => self.with_span(SynNode::Linebreak), Token::Hashtag if was_at_block_or_line_start => { - self.parse_heading().map(SyntaxNode::Heading) + self.parse_heading().map(SynNode::Heading) } Token::Raw { raw, backticks, terminated } => { @@ -116,11 +111,11 @@ impl Parser<'_> { } let raw = resolve::resolve_raw(raw, backticks); - self.with_span(SyntaxNode::Raw(raw)) + self.with_span(SynNode::Raw(raw)) } - Token::Text(text) => self.with_span(SyntaxNode::Text(text.to_string())), - Token::Hashtag => self.with_span(SyntaxNode::Text("#".to_string())), + Token::Text(text) => self.with_span(SynNode::Text(text.to_string())), + Token::Hashtag => self.with_span(SynNode::Text("#".to_string())), Token::UnicodeEscape { sequence, terminated } => { if !terminated { @@ -128,7 +123,7 @@ impl Parser<'_> { } if let Some(c) = resolve::resolve_hex(sequence) { - self.with_span(SyntaxNode::Text(c.to_string())) + self.with_span(SynNode::Text(c.to_string())) } else { error!(@self.feedback, token.span, "invalid unicode escape sequence"); // TODO: Decide whether to render the escape sequence. @@ -145,7 +140,7 @@ impl Parser<'_> { }) } - fn parse_heading(&mut self) -> Spanned { + fn parse_heading(&mut self) -> Spanned { let start = self.pos(); self.assert(Token::Hashtag); @@ -167,7 +162,7 @@ impl Parser<'_> { self.skip_ws(); - let mut tree = SyntaxTree::new(); + let mut tree = SynTree::new(); while !self.eof() && !matches!(self.peekv(), Some(Token::Space(n)) if n >= 1) { if let Some(node) = self.parse_node() { tree.push(node); @@ -175,13 +170,13 @@ impl Parser<'_> { } let span = Span::new(start, self.pos()); - Heading { level, tree }.span_with(span) + NodeHeading { level, contents: tree }.span_with(span) } } // Function calls. impl Parser<'_> { - fn parse_bracket_call(&mut self, chained: bool) -> Spanned { + fn parse_bracket_call(&mut self, chained: bool) -> Spanned { let before_bracket = self.pos(); if !chained { self.start_group(Group::Bracket); @@ -203,9 +198,9 @@ impl Parser<'_> { Some(_) => { self.expected_at("colon", name.span.end); while self.eat().is_some() {} - DictExpr::new() + LitDict::default() } - None => DictExpr::new(), + None => LitDict::default(), }; self.end_group(); @@ -213,8 +208,9 @@ impl Parser<'_> { let (has_chained_child, end) = if self.peek().is_some() { let item = self.parse_bracket_call(true); let span = item.span; - let t = vec![item.map(SyntaxNode::Call)]; - args.push(SpannedEntry::val(Expr::Tree(t).span_with(span))); + let tree = vec![item.map(|c| SynNode::Expr(Expr::Call(c)))]; + let expr = Expr::Lit(Lit::Content(tree)); + args.0.push(LitDictEntry { key: None, value: expr.span_with(span) }); (true, span.end) } else { self.tokens.pop_mode(); @@ -227,40 +223,41 @@ impl Parser<'_> { if self.check(Token::LeftBracket) && !has_chained_child { self.start_group(Group::Bracket); self.tokens.push_mode(TokenMode::Body); - let body = self.parse_body_contents(); - self.tokens.pop_mode(); let body_span = self.end_group(); - let expr = Expr::Tree(body); - args.push(SpannedEntry::val(expr.span_with(body_span))); + let expr = Expr::Lit(Lit::Content(body)); + args.0.push(LitDictEntry { + key: None, + value: expr.span_with(body_span), + }); span.expand(body_span); } - CallExpr { name, args }.span_with(span) + ExprCall { name, args }.span_with(span) } - fn parse_paren_call(&mut self, name: Spanned) -> Spanned { + fn parse_paren_call(&mut self, name: Spanned) -> Spanned { self.start_group(Group::Paren); let args = self.parse_dict_contents().0; let args_span = self.end_group(); let span = Span::merge(name.span, args_span); - CallExpr { name, args }.span_with(span) + ExprCall { name, args }.span_with(span) } } // Dicts. impl Parser<'_> { - fn parse_dict_contents(&mut self) -> (DictExpr, bool) { - let mut dict = DictExpr::new(); + fn parse_dict_contents(&mut self) -> (LitDict, bool) { + let mut dict = LitDict::default(); let mut comma_and_keyless = true; while { self.skip_ws(); !self.eof() } { - let (key, val) = if let Some(ident) = self.parse_ident() { + let (key, value) = if let Some(ident) = self.parse_ident() { self.skip_ws(); match self.peekv() { @@ -268,7 +265,7 @@ impl Parser<'_> { self.eat(); self.skip_ws(); if let Some(value) = self.parse_expr() { - (Some(ident), value) + (Some(ident.map(|id| DictKey::Str(id.0))), value) } else { self.expected("value"); continue; @@ -280,7 +277,7 @@ impl Parser<'_> { (None, call.map(Expr::Call)) } - _ => (None, ident.map(Expr::Ident)), + _ => (None, ident.map(|id| Expr::Lit(Lit::Ident(id)))), } } else if let Some(value) = self.parse_expr() { (None, value) @@ -289,17 +286,16 @@ impl Parser<'_> { continue; }; - let behind = val.span.end; - if let Some(key) = key { + if let Some(key) = &key { comma_and_keyless = false; - dict.insert(key.v.0, SpannedEntry::new(key.span, val)); self.feedback .decorations .push(Decoration::DictKey.span_with(key.span)); - } else { - dict.push(SpannedEntry::val(val)); } + let behind = value.span.end; + dict.0.push(LitDictEntry { key, value }); + if { self.skip_ws(); self.eof() @@ -311,27 +307,25 @@ impl Parser<'_> { comma_and_keyless = false; } - let coercable = comma_and_keyless && !dict.is_empty(); + let coercable = comma_and_keyless && !dict.0.is_empty(); (dict, coercable) } } -type Binop = fn(Box>, Box>) -> Expr; - // Expressions and values. impl Parser<'_> { fn parse_expr(&mut self) -> Option> { self.parse_binops("summand", Self::parse_term, |token| match token { - Token::Plus => Some(Expr::Add), - Token::Hyphen => Some(Expr::Sub), + Token::Plus => Some(BinOp::Add), + Token::Hyphen => Some(BinOp::Sub), _ => None, }) } fn parse_term(&mut self) -> Option> { self.parse_binops("factor", Self::parse_factor, |token| match token { - Token::Star => Some(Expr::Mul), - Token::Slash => Some(Expr::Div), + Token::Star => Some(BinOp::Mul), + Token::Slash => Some(BinOp::Div), _ => None, }) } @@ -341,7 +335,7 @@ impl Parser<'_> { &mut self, operand_name: &str, mut parse_operand: impl FnMut(&mut Self) -> Option>, - mut parse_op: impl FnMut(Token) -> Option, + mut parse_op: impl FnMut(Token) -> Option, ) -> Option> { let mut left = parse_operand(self)?; @@ -353,8 +347,12 @@ impl Parser<'_> { if let Some(right) = parse_operand(self) { let span = Span::merge(left.span, right.span); - let v = op(Box::new(left), Box::new(right)); - left = v.span_with(span); + let expr = Expr::Binary(ExprBinary { + lhs: left.map(Box::new), + op: op.span_with(token.span), + rhs: right.map(Box::new), + }); + left = expr.span_with(span); self.skip_ws(); continue; } @@ -375,7 +373,11 @@ impl Parser<'_> { self.skip_ws(); if let Some(factor) = self.parse_factor() { let span = Span::merge(hyph.span, factor.span); - Some(Expr::Neg(Box::new(factor)).span_with(span)) + let expr = Expr::Unary(ExprUnary { + op: UnOp::Neg.span_with(hyph.span), + expr: factor.map(Box::new), + }); + Some(expr.span_with(span)) } else { error!(@self.feedback, hyph.span, "dangling minus"); None @@ -396,7 +398,7 @@ impl Parser<'_> { if self.check(Token::LeftParen) { self.parse_paren_call(name).map(Expr::Call) } else { - name.map(Expr::Ident) + name.map(|n| Expr::Lit(Lit::Ident(n))) } } @@ -404,21 +406,19 @@ impl Parser<'_> { if !terminated { self.expected_at("quote", span.end); } - self.with_span(Expr::Str(resolve::resolve_string(string))) + self.with_span(Expr::Lit(Lit::Str(resolve::resolve_string(string)))) } - Token::Bool(b) => self.with_span(Expr::Bool(b)), - Token::Number(n) => self.with_span(Expr::Number(n)), - Token::Length(s) => self.with_span(Expr::Length(s)), + Token::Bool(b) => self.with_span(Expr::Lit(Lit::Bool(b))), + Token::Number(n) => self.with_span(Expr::Lit(Lit::Float(n))), + Token::Length(s) => self.with_span(Expr::Lit(Lit::Length(s))), Token::Hex(s) => { - if let Ok(color) = RgbaColor::from_str(s) { - self.with_span(Expr::Color(color)) - } else { + let color = RgbaColor::from_str(s).unwrap_or_else(|_| { // Heal color by assuming black. error!(@self.feedback, span, "invalid color"); - let healed = RgbaColor::new_healed(0, 0, 0, 255); - self.with_span(Expr::Color(healed)) - } + RgbaColor::new_healed(0, 0, 0, 255) + }); + self.with_span(Expr::Lit(Lit::Color(color))) } // This could be a dictionary or a parenthesized expression. We @@ -430,9 +430,9 @@ impl Parser<'_> { let span = self.end_group(); let expr = if coercable { - dict.into_values().next().expect("dict is coercable").val.v + dict.0.into_iter().next().expect("dict is coercable").value.v } else { - Expr::Dict(dict) + Expr::Lit(Lit::Dict(dict)) }; expr.span_with(span) @@ -442,19 +442,17 @@ impl Parser<'_> { Token::LeftBrace => { self.start_group(Group::Brace); self.tokens.push_mode(TokenMode::Body); - let tree = self.parse_body_contents(); - self.tokens.pop_mode(); let span = self.end_group(); - Expr::Tree(tree).span_with(span) + Expr::Lit(Lit::Content(tree)).span_with(span) } // This is a bracketed function call. Token::LeftBracket => { let call = self.parse_bracket_call(false); - let tree = vec![call.map(SyntaxNode::Call)]; - Expr::Tree(tree).span_with(span) + let tree = vec![call.map(|c| SynNode::Expr(Expr::Call(c)))]; + Expr::Lit(Lit::Content(tree)).span_with(span) } _ => return None, diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs index d4babd259..f99193739 100644 --- a/src/parse/resolve.rs +++ b/src/parse/resolve.rs @@ -1,7 +1,7 @@ //! Resolve strings and raw blocks. use super::{is_newline, Scanner}; -use crate::syntax::{Ident, Raw}; +use crate::syntax::{Ident, NodeRaw}; /// Resolves all escape sequences in a string. pub fn resolve_string(string: &str) -> String { @@ -49,17 +49,17 @@ pub fn resolve_hex(sequence: &str) -> Option { } /// Resolves the language tag and trims the raw text. -pub fn resolve_raw(raw: &str, backticks: usize) -> Raw { +pub fn resolve_raw(raw: &str, backticks: usize) -> NodeRaw { if backticks > 1 { let (tag, inner) = split_at_lang_tag(raw); let (lines, had_newline) = trim_and_split_raw(inner); - Raw { + NodeRaw { lang: Ident::new(tag), lines, inline: !had_newline, } } else { - Raw { + NodeRaw { lang: None, lines: split_lines(raw), inline: true, diff --git a/src/parse/tests.rs b/src/parse/tests.rs index e516af328..302e93eeb 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -6,33 +6,33 @@ use std::fmt::Debug; use super::parse; use crate::color::RgbaColor; -use crate::compute::dict::SpannedEntry; +use crate::compute::dict::DictKey; use crate::length::Length; use crate::syntax::*; // ------------------------------ Construct Syntax Nodes ------------------------------ // use Decoration::*; -use SyntaxNode::{ +use SynNode::{ Linebreak as L, Parbreak as P, Spacing as S, ToggleBolder as B, ToggleItalic as I, }; -fn T(text: &str) -> SyntaxNode { - SyntaxNode::Text(text.to_string()) +fn T(text: &str) -> SynNode { + SynNode::Text(text.to_string()) } macro_rules! H { ($level:expr, $($tts:tt)*) => { - SyntaxNode::Heading(Heading { + SynNode::Heading(NodeHeading { level: Spanned::zero($level), - tree: Tree![@$($tts)*], + contents: Tree![@$($tts)*], }) }; } macro_rules! R { ($lang:expr, $inline:expr, $($line:expr),* $(,)?) => {{ - SyntaxNode::Raw(Raw { + SynNode::Raw(NodeRaw { lang: $lang, lines: vec![$($line.to_string()) ,*], inline: $inline, @@ -45,53 +45,73 @@ fn Lang(lang: &str) -> Option { } macro_rules! F { - ($($tts:tt)*) => { SyntaxNode::Call(Call!(@$($tts)*)) } + ($($tts:tt)*) => { SynNode::Expr(Expr::Call(Call!(@$($tts)*))) } } // ------------------------------- Construct Expressions ------------------------------ // -use Expr::{Bool, Color, Length as Len, Number as Num}; +use BinOp::*; +use UnOp::*; fn Id(ident: &str) -> Expr { - Expr::Ident(Ident(ident.to_string())) + Expr::Lit(Lit::Ident(Ident(ident.to_string()))) +} +fn Bool(b: bool) -> Expr { + Expr::Lit(Lit::Bool(b)) +} +fn _Int(int: i64) -> Expr { + Expr::Lit(Lit::Int(int)) +} +fn Float(float: f64) -> Expr { + Expr::Lit(Lit::Float(float)) +} +fn _Percent(percent: f64) -> Expr { + Expr::Lit(Lit::Percent(percent)) +} +fn Len(length: Length) -> Expr { + Expr::Lit(Lit::Length(length)) +} +fn Color(color: RgbaColor) -> Expr { + Expr::Lit(Lit::Color(color)) } fn Str(string: &str) -> Expr { - Expr::Str(string.to_string()) + Expr::Lit(Lit::Str(string.to_string())) } macro_rules! Dict { (@dict=$dict:expr,) => {}; (@dict=$dict:expr, $key:expr => $value:expr $(, $($tts:tt)*)?) => {{ let key = Into::>::into($key); - let val = Into::>::into($value); - $dict.insert(key.v, SpannedEntry::new(key.span, val)); + let key = key.map(Into::::into); + let value = Into::>::into($value); + $dict.0.push(LitDictEntry { key: Some(key), value }); Dict![@dict=$dict, $($($tts)*)?]; }}; (@dict=$dict:expr, $value:expr $(, $($tts:tt)*)?) => { - let val = Into::>::into($value); - $dict.push(SpannedEntry::val(val)); + let value = Into::>::into($value); + $dict.0.push(LitDictEntry { key: None, value }); Dict![@dict=$dict, $($($tts)*)?]; }; (@$($tts:tt)*) => {{ #[allow(unused_mut)] - let mut dict = DictExpr::new(); + let mut dict = LitDict::default(); Dict![@dict=dict, $($tts)*]; dict }}; - ($($tts:tt)*) => { Expr::Dict(Dict![@$($tts)*]) }; + ($($tts:tt)*) => { Expr::Lit(Lit::Dict(Dict![@$($tts)*])) }; } macro_rules! Tree { (@$($node:expr),* $(,)?) => { - vec![$(Into::>::into($node)),*] + vec![$(Into::>::into($node)),*] }; - ($($tts:tt)*) => { Expr::Tree(Tree![@$($tts)*]) }; + ($($tts:tt)*) => { Expr::Lit(Lit::Content(Tree![@$($tts)*])) }; } macro_rules! Call { (@$name:expr $(; $($tts:tt)*)?) => {{ let name = Into::>::into($name); - CallExpr { + ExprCall { name: name.map(|n| Ident(n.to_string())), args: Dict![@$($($tts)*)?], } @@ -99,20 +119,22 @@ macro_rules! Call { ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) }; } -fn Neg>>(e1: T) -> Expr { - Expr::Neg(Box::new(e1.into())) +fn Unary(op: impl Into>, expr: impl Into>) -> Expr { + Expr::Unary(ExprUnary { + op: op.into(), + expr: expr.into().map(Box::new), + }) } -fn Add>>(e1: T, e2: T) -> Expr { - Expr::Add(Box::new(e1.into()), Box::new(e2.into())) -} -fn Sub>>(e1: T, e2: T) -> Expr { - Expr::Sub(Box::new(e1.into()), Box::new(e2.into())) -} -fn Mul>>(e1: T, e2: T) -> Expr { - Expr::Mul(Box::new(e1.into()), Box::new(e2.into())) -} -fn Div>>(e1: T, e2: T) -> Expr { - Expr::Div(Box::new(e1.into()), Box::new(e2.into())) +fn Binary( + op: impl Into>, + lhs: impl Into>, + rhs: impl Into>, +) -> Expr { + Expr::Binary(ExprBinary { + lhs: lhs.into().map(Box::new), + op: op.into(), + rhs: rhs.into().map(Box::new), + }) } // ------------------------------------ Test Macros ----------------------------------- // @@ -321,12 +343,12 @@ fn test_parse_function_names() { #[test] fn test_parse_chaining() { // Things the parser has to make sense of - t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Dict![Num(5.0), Num(2.1)], Tree![F!("you")])); + t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Dict![Float(5.0), Float(2.1)], Tree![F!("you")])); t!("[box >>][Hi]" => F!("box"; Tree![T("Hi")])); t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![ F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi"))) ])); - t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Num(400.0), Tree![ + t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Float(400.0), Tree![ F!("emph"; Tree!(F!("sub"; Len(Length::cm(1.0))))) ])); @@ -354,7 +376,7 @@ fn test_parse_colon_starting_func_args() { #[test] fn test_parse_function_bodies() { - t!("[val: 1][*Hi*]" => F!("val"; Num(1.0), Tree![B, T("Hi"), B])); + t!("[val: 1][*Hi*]" => F!("val"; Float(1.0), Tree![B, T("Hi"), B])); e!(" [val][ */]" => s(8, 10, "unexpected end of block comment")); // Raw in body. @@ -384,9 +406,9 @@ fn test_parse_values() { v!("\"hi\"" => Str("hi")); v!("true" => Bool(true)); v!("false" => Bool(false)); - v!("1.0e-4" => Num(1e-4)); - v!("3.14" => Num(3.14)); - v!("50%" => Num(0.5)); + v!("1.0e-4" => Float(1e-4)); + v!("3.14" => Float(3.14)); + v!("50%" => Float(0.5)); v!("4.5cm" => Len(Length::cm(4.5))); v!("12e1pt" => Len(Length::pt(12e1))); v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00))); @@ -411,7 +433,7 @@ fn test_parse_values() { s(13, 13, "expected closing bracket")); // Spanned. - ts!("[val: 1.4]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Num(1.4))))); + ts!("[val: 1.4]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Float(1.4))))); } #[test] @@ -420,40 +442,50 @@ fn test_parse_expressions() { v!("(hi)" => Id("hi")); // Operations. - v!("-1" => Neg(Num(1.0))); - v!("-- 1" => Neg(Neg(Num(1.0)))); - v!("3.2in + 6pt" => Add(Len(Length::inches(3.2)), Len(Length::pt(6.0)))); - v!("5 - 0.01" => Sub(Num(5.0), Num(0.01))); - v!("(3mm * 2)" => Mul(Len(Length::mm(3.0)), Num(2.0))); - v!("12e-3cm/1pt" => Div(Len(Length::cm(12e-3)), Len(Length::pt(1.0)))); + v!("-1" => Unary(Neg, Float(1.0))); + v!("-- 1" => Unary(Neg, Unary(Neg, Float(1.0)))); + v!("3.2in + 6pt" => Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0)))); + v!("5 - 0.01" => Binary(Sub, Float(5.0), Float(0.01))); + v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Float(2.0))); + v!("12e-3cm/1pt" => Binary(Div, Len(Length::cm(12e-3)), Len(Length::pt(1.0)))); // More complex. - v!("(3.2in + 6pt)*(5/2-1)" => Mul( - Add(Len(Length::inches(3.2)), Len(Length::pt(6.0))), - Sub(Div(Num(5.0), Num(2.0)), Num(1.0)) + v!("(3.2in + 6pt)*(5/2-1)" => Binary( + Mul, + Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))), + Binary(Sub, Binary(Div, Float(5.0), Float(2.0)), Float(1.0)) )); - v!("(6.3E+2+4* - 3.2pt)/2" => Div( - Add(Num(6.3e2), Mul(Num(4.0), Neg(Len(Length::pt(3.2))))), - Num(2.0) + v!("(6.3E+2+4* - 3.2pt)/2" => Binary( + Div, + Binary(Add, Float(6.3e2), Binary( + Mul, + Float(4.0), + Unary(Neg, Len(Length::pt(3.2))) + )), + Float(2.0) )); // Associativity of multiplication and division. - v!("3/4*5" => Mul(Div(Num(3.0), Num(4.0)), Num(5.0))); + v!("3/4*5" => Binary(Mul, Binary(Div, Float(3.0), Float(4.0)), Float(5.0))); // Spanned. ts!("[val: 1 + 3]" => s(0, 12, F!( - s(1, 4, "val"); s(6, 11, Add(s(6, 7, Num(1.0)), s(10, 11, Num(3.0)))) + s(1, 4, "val"); s(6, 11, Binary( + s(8, 9, Add), + s(6, 7, Float(1.0)), + s(10, 11, Float(3.0)) + )) ))); // Span of parenthesized expression contains parens. - ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Num(1.0))))); + ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Float(1.0))))); // Invalid expressions. v!("4pt--" => Len(Length::pt(4.0))); e!("[val: 4pt--]" => s(10, 11, "dangling minus"), s(6, 10, "missing right summand")); - v!("3mm+4pt*" => Add(Len(Length::mm(3.0)), Len(Length::pt(4.0)))); + v!("3mm+4pt*" => Binary(Add, Len(Length::mm(3.0)), Len(Length::pt(4.0)))); e!("[val: 3mm+4pt*]" => s(10, 14, "missing right factor")); } @@ -464,8 +496,8 @@ fn test_parse_dicts() { v!("(false)" => Bool(false)); v!("(true,)" => Dict![Bool(true)]); v!("(key=val)" => Dict!["key" => Id("val")]); - v!("(1, 2)" => Dict![Num(1.0), Num(2.0)]); - v!("(1, key=\"value\")" => Dict![Num(1.0), "key" => Str("value")]); + v!("(1, 2)" => Dict![Float(1.0), Float(2.0)]); + v!("(1, key=\"value\")" => Dict![Float(1.0), "key" => Str("value")]); // Decorations. d!("[val: key=hi]" => s(6, 9, DictKey)); @@ -483,7 +515,7 @@ fn test_parse_dicts() { #[test] fn test_parse_dicts_compute_func_calls() { v!("empty()" => Call!("empty")); - v!("add ( 1 , 2 )" => Call!("add"; Num(1.0), Num(2.0))); + v!("add ( 1 , 2 )" => Call!("add"; Float(1.0), Float(2.0))); v!("items(\"fire\", #f93a6d)" => Call!("items"; Str("fire"), Color(RgbaColor::new(0xf9, 0x3a, 0x6d, 0xff)) )); @@ -492,7 +524,7 @@ fn test_parse_dicts_compute_func_calls() { v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!( "css"; Len(Length::pt(1.0)), - Call!("rgb"; Num(90.0), Num(102.0), Num(254.0)), + Call!("rgb"; Float(90.0), Float(102.0), Float(254.0)), Str("solid"), )); @@ -501,7 +533,7 @@ fn test_parse_dicts_compute_func_calls() { e!("[val: lang(δΈ­ζ–‡]" => s(17, 17, "expected closing paren")); // Invalid name. - v!("πŸ‘ (\"abc\", 13e-5)" => Dict!(Str("abc"), Num(13.0e-5))); + v!("πŸ‘ (\"abc\", 13e-5)" => Dict!(Str("abc"), Float(13.0e-5))); e!("[val: πŸ‘ (\"abc\", 13e-5)]" => s(6, 10, "expected value, found invalid token")); } @@ -509,10 +541,10 @@ fn test_parse_dicts_compute_func_calls() { fn test_parse_dicts_nested() { v!("(1, ( ab=(), d = (3, 14pt) )), false" => Dict![ - Num(1.0), + Float(1.0), Dict!( "ab" => Dict![], - "d" => Dict!(Num(3.0), Len(Length::pt(14.0))), + "d" => Dict!(Float(3.0), Len(Length::pt(14.0))), ), ], Bool(false), @@ -546,7 +578,7 @@ fn test_parse_dicts_errors() { s(10, 11, "expected value, found equals sign")); // Unexpected equals sign. - v!("z=y=4" => Num(4.0), "z" => Id("y")); + v!("z=y=4" => "z" => Id("y"), Float(4.0)); e!("[val: z=y=4]" => s(9, 9, "expected comma"), s(9, 10, "expected value, found equals sign")); diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs new file mode 100644 index 000000000..817b3d6e9 --- /dev/null +++ b/src/syntax/expr.rs @@ -0,0 +1,124 @@ +//! Expressions. + +use super::span::{SpanWith, Spanned}; +use super::{Decoration, Ident, Lit, LitDict}; +use crate::compute::value::Value; +use crate::layout::LayoutContext; +use crate::Feedback; + +/// An expression. +#[derive(Debug, Clone, PartialEq)] +pub enum Expr { + /// A literal: `true`, `1cm`, `"hi"`, `{_Hey!_}`. + Lit(Lit), + /// A unary operation: `-x`. + Unary(ExprUnary), + /// A binary operation: `a + b`, `a / b`. + Binary(ExprBinary), + /// An invocation of a function: `[foo: ...]`, `foo(...)`. + Call(ExprCall), +} + +impl Expr { + /// Evaluate the expression to a value. + pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + match self { + Self::Lit(lit) => lit.eval(ctx, f).await, + Self::Unary(unary) => unary.eval(ctx, f).await, + Self::Binary(binary) => binary.eval(ctx, f).await, + Self::Call(call) => call.eval(ctx, f).await, + } + } +} + +/// A unary operation: `-x`. +#[derive(Debug, Clone, PartialEq)] +pub struct ExprUnary { + /// The operator: `-`. + pub op: Spanned, + /// The expression to operator on: `x`. + pub expr: Spanned>, +} + +impl ExprUnary { + /// Evaluate the expression to a value. + pub async fn eval(&self, _: &LayoutContext<'_>, _: &mut Feedback) -> Value { + match self.op.v { + UnOp::Neg => todo!("eval neg"), + } + } +} + +/// A unary operator. +#[derive(Debug, Clone, PartialEq)] +pub enum UnOp { + /// The negation operator: `-`. + Neg, +} + +/// A binary operation: `a + b`, `a / b`. +#[derive(Debug, Clone, PartialEq)] +pub struct ExprBinary { + /// The left-hand side of the operation: `a`. + pub lhs: Spanned>, + /// The operator: `+`. + pub op: Spanned, + /// The right-hand side of the operation: `b`. + pub rhs: Spanned>, +} + +impl ExprBinary { + /// Evaluate the expression to a value. + pub async fn eval(&self, _: &LayoutContext<'_>, _: &mut Feedback) -> Value { + match self.op.v { + BinOp::Add => todo!("eval add"), + BinOp::Sub => todo!("eval sub"), + BinOp::Mul => todo!("eval mul"), + BinOp::Div => todo!("eval div"), + } + } +} + +/// A binary operator. +#[derive(Debug, Clone, PartialEq)] +pub enum BinOp { + /// The addition operator: `+`. + Add, + /// The subtraction operator: `-`. + Sub, + /// The multiplication operator: `*`. + Mul, + /// The division operator: `/`. + Div, +} + +/// An invocation of a function: `[foo: ...]`, `foo(...)`. +#[derive(Debug, Clone, PartialEq)] +pub struct ExprCall { + /// The name of the function. + pub name: Spanned, + /// The arguments to the function. + pub args: LitDict, +} + +impl ExprCall { + /// Evaluate the call expression to a value. + pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + let name = &self.name.v; + let span = self.name.span; + let args = self.args.eval(ctx, f).await; + + if let Some(func) = ctx.scope.func(name) { + let pass = func(span, args, ctx.clone()).await; + f.extend(pass.feedback); + f.decorations.push(Decoration::Resolved.span_with(span)); + pass.output + } else { + if !name.is_empty() { + error!(@f, span, "unknown function"); + f.decorations.push(Decoration::Unresolved.span_with(span)); + } + Value::Dict(args) + } + } +} diff --git a/src/syntax/ident.rs b/src/syntax/ident.rs new file mode 100644 index 000000000..5ab7c73c4 --- /dev/null +++ b/src/syntax/ident.rs @@ -0,0 +1,58 @@ +//! Unicode identifiers. + +use std::ops::Deref; + +use unicode_xid::UnicodeXID; + +/// An identifier as defined by unicode with a few extra permissible characters. +/// +/// This is defined as in the [Unicode Standard], but allows additionally +/// `-` and `_` as starting and continuing characters. +/// +/// [Unicode Standard]: http://www.unicode.org/reports/tr31/ +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Ident(pub String); + +impl Ident { + /// Create a new identifier from a string checking that it is a valid. + pub fn new(ident: impl AsRef + Into) -> Option { + if is_ident(ident.as_ref()) { + Some(Self(ident.into())) + } else { + None + } + } + + /// Return a reference to the underlying string. + pub fn as_str(&self) -> &str { + self + } +} + +impl AsRef for Ident { + fn as_ref(&self) -> &str { + self + } +} + +impl Deref for Ident { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_str() + } +} + +/// Whether the string is a valid identifier. +pub fn is_ident(string: &str) -> bool { + let mut chars = string.chars(); + if matches!(chars.next(), Some(c) if c.is_xid_start() || is_also_ok(c)) { + chars.all(|c| c.is_xid_continue() || is_also_ok(c)) + } else { + false + } +} + +fn is_also_ok(c: char) -> bool { + c == '-' || c == '_' +} diff --git a/src/syntax/lit.rs b/src/syntax/lit.rs new file mode 100644 index 000000000..e9807a178 --- /dev/null +++ b/src/syntax/lit.rs @@ -0,0 +1,99 @@ +//! Literals. + +use super::{Expr, Ident, SpanWith, Spanned, SynTree}; +use crate::color::RgbaColor; +use crate::compute::dict::{DictKey, SpannedEntry}; +use crate::compute::value::{DictValue, Value}; +use crate::layout::LayoutContext; +use crate::length::Length; +use crate::{DynFuture, Feedback}; + +/// A literal. +#[derive(Debug, Clone, PartialEq)] +pub enum Lit { + /// A identifier literal: `left`. + Ident(Ident), + /// A boolean literal: `true`, `false`. + Bool(bool), + /// An integer literal: `120`. + Int(i64), + /// A floating-point literal: `1.2`, `10e-4`. + Float(f64), + /// A percent literal: `50%`. + Percent(f64), + /// A length literal: `12pt`, `3cm`. + Length(Length), + /// A color literal: `#ffccee`. + Color(RgbaColor), + /// A string literal: `"hello!"`. + Str(String), + /// A dictionary literal: `(false, 12cm, greeting = "hi")`. + Dict(LitDict), + /// A content literal: `{*Hello* there!}`. + Content(SynTree), +} + +impl Lit { + /// Evaluate the dictionary literal to a dictionary value. + pub async fn eval<'a>( + &'a self, + ctx: &'a LayoutContext<'a>, + f: &'a mut Feedback, + ) -> Value { + match *self { + Lit::Ident(ref i) => Value::Ident(i.clone()), + Lit::Bool(b) => Value::Bool(b), + Lit::Int(i) => Value::Number(i as f64), + Lit::Float(f) => Value::Number(f as f64), + Lit::Percent(p) => Value::Number(p as f64 / 100.0), + Lit::Length(l) => Value::Length(l), + Lit::Color(c) => Value::Color(c), + Lit::Str(ref s) => Value::Str(s.clone()), + Lit::Dict(ref d) => Value::Dict(d.eval(ctx, f).await), + Lit::Content(ref c) => Value::Tree(c.clone()), + } + } +} + +/// A dictionary literal: `(false, 12cm, greeting = "hi")`. +#[derive(Debug, Default, Clone, PartialEq)] +pub struct LitDict(pub Vec); + +impl LitDict { + /// Create an empty dict literal. + pub fn new() -> Self { + Self(vec![]) + } + + /// Evaluate the dictionary literal to a dictionary value. + pub fn eval<'a>( + &'a self, + ctx: &'a LayoutContext<'a>, + f: &'a mut Feedback, + ) -> DynFuture<'a, DictValue> { + Box::pin(async move { + let mut dict = DictValue::new(); + + for entry in &self.0 { + let val = entry.value.v.eval(ctx, f).await; + let spanned = val.span_with(entry.value.span); + if let Some(key) = &entry.key { + dict.insert(&key.v, SpannedEntry::new(key.span, spanned)); + } else { + dict.push(SpannedEntry::val(spanned)); + } + } + + dict + }) + } +} + +/// An entry in a dictionary literal: `false` or `greeting = "hi"`. +#[derive(Debug, Clone, PartialEq)] +pub struct LitDictEntry { + /// The key of the entry if there was one: `greeting`. + pub key: Option>, + /// The value of the entry: `"hi"`. + pub value: Spanned, +} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 0809d33f0..f4472df55 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -1,12 +1,24 @@ //! Syntax types. +mod expr; +mod ident; +mod lit; mod span; mod token; mod tree; +/// Abstract syntax tree definition. +pub mod ast { + use super::*; + pub use expr::*; + pub use lit::*; + pub use tree::*; +} + +pub use ast::*; +pub use ident::*; pub use span::*; pub use token::*; -pub use tree::*; /// Decorations for semantic syntax highlighting. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] diff --git a/src/syntax/span.rs b/src/syntax/span.rs index eb029479f..629297063 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -42,8 +42,10 @@ impl Offset for SpanVec { #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] pub struct Spanned { - pub span: Span, + /// The spanned value. pub v: T, + /// The location in source code of the value. + pub span: Span, } impl Spanned { diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs index c4ce3ad26..80bca3990 100644 --- a/src/syntax/tree.rs +++ b/src/syntax/tree.rs @@ -1,26 +1,15 @@ //! The syntax tree. -use std::fmt::{self, Debug, Formatter}; -use std::ops::Deref; - -use unicode_xid::UnicodeXID; - -use super::span::{SpanVec, SpanWith, Spanned}; -use super::Decoration; -use crate::color::RgbaColor; -use crate::compute::dict::{Dict, SpannedEntry}; -use crate::compute::value::{DictValue, Value}; -use crate::layout::LayoutContext; -use crate::length::Length; -use crate::{DynFuture, Feedback}; +use super::span::{SpanVec, Spanned}; +use super::{Expr, Ident}; /// A collection of nodes which form a tree together with the nodes' children. -pub type SyntaxTree = SpanVec; +pub type SynTree = SpanVec; /// A syntax node, which encompasses a single logical entity of parsed source /// code. #[derive(Debug, Clone, PartialEq)] -pub enum SyntaxNode { +pub enum SynNode { /// Whitespace containing less than two newlines. Spacing, /// A forced line break. @@ -34,11 +23,11 @@ pub enum SyntaxNode { /// Plain text. Text(String), /// An optionally syntax-highlighted raw block. - Raw(Raw), - /// Section headings. - Heading(Heading), - /// A function call. - Call(CallExpr), + Raw(NodeRaw), + /// A section heading. + Heading(NodeHeading), + /// An expression. + Expr(Expr), } /// A raw block, rendered in monospace with optional syntax highlighting. @@ -103,7 +92,7 @@ pub enum SyntaxNode { /// you can always force leading or trailing whitespace simply by adding more /// spaces. #[derive(Debug, Clone, PartialEq)] -pub struct Raw { +pub struct NodeRaw { /// An optional identifier specifying the language to syntax-highlight in. pub lang: Option, /// The lines of raw text, determined as the raw string between the @@ -122,221 +111,9 @@ pub struct Raw { /// A section heading. #[derive(Debug, Clone, PartialEq)] -pub struct Heading { +pub struct NodeHeading { /// The section depth (how many hashtags minus 1). pub level: Spanned, - pub tree: SyntaxTree, -} - -/// An expression. -#[derive(Clone, PartialEq)] -pub enum Expr { - /// An identifier: `ident`. - Ident(Ident), - /// A string: `"string"`. - Str(String), - /// A boolean: `true, false`. - Bool(bool), - /// A number: `1.2, 200%`. - Number(f64), - /// A length: `2cm, 5.2in`. - Length(Length), - /// A color value with alpha channel: `#f79143ff`. - Color(RgbaColor), - /// A dictionary expression: `(false, 12cm, greeting="hi")`. - Dict(DictExpr), - /// A syntax tree containing typesetting content. - Tree(SyntaxTree), - /// A function call expression: `cmyk(37.7, 0, 3.9, 1.1)`. - Call(CallExpr), - /// An operation that negates the contained expression. - Neg(Box>), - /// An operation that adds the contained expressions. - Add(Box>, Box>), - /// An operation that subtracts the contained expressions. - Sub(Box>, Box>), - /// An operation that multiplies the contained expressions. - Mul(Box>, Box>), - /// An operation that divides the contained expressions. - Div(Box>, Box>), -} - -impl Expr { - /// A natural-language name of the type of this expression, e.g. - /// "identifier". - pub fn name(&self) -> &'static str { - use Expr::*; - match self { - Ident(_) => "identifier", - Str(_) => "string", - Bool(_) => "bool", - Number(_) => "number", - Length(_) => "length", - Color(_) => "color", - Dict(_) => "dictg", - Tree(_) => "syntax tree", - Call(_) => "function call", - Neg(_) => "negation", - Add(_, _) => "addition", - Sub(_, _) => "subtraction", - Mul(_, _) => "multiplication", - Div(_, _) => "division", - } - } - - /// Evaluate the expression to a value. - pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { - use Expr::*; - match self { - Ident(i) => Value::Ident(i.clone()), - Str(s) => Value::Str(s.clone()), - &Bool(b) => Value::Bool(b), - &Number(n) => Value::Number(n), - &Length(s) => Value::Length(s), - &Color(c) => Value::Color(c), - Dict(t) => Value::Dict(t.eval(ctx, f).await), - Tree(t) => Value::Tree(t.clone()), - Call(call) => call.eval(ctx, f).await, - Neg(_) => todo!("eval neg"), - Add(_, _) => todo!("eval add"), - Sub(_, _) => todo!("eval sub"), - Mul(_, _) => todo!("eval mul"), - Div(_, _) => todo!("eval div"), - } - } -} - -impl Debug for Expr { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use Expr::*; - match self { - Ident(i) => i.fmt(f), - Str(s) => s.fmt(f), - Bool(b) => b.fmt(f), - Number(n) => n.fmt(f), - Length(s) => s.fmt(f), - Color(c) => c.fmt(f), - Dict(t) => t.fmt(f), - Tree(t) => t.fmt(f), - Call(c) => c.fmt(f), - Neg(e) => write!(f, "-{:?}", e), - Add(a, b) => write!(f, "({:?} + {:?})", a, b), - Sub(a, b) => write!(f, "({:?} - {:?})", a, b), - Mul(a, b) => write!(f, "({:?} * {:?})", a, b), - Div(a, b) => write!(f, "({:?} / {:?})", a, b), - } - } -} - -/// An identifier as defined by unicode with a few extra permissible characters. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Ident(pub String); - -impl Ident { - /// Create a new identifier from a string checking that it is a valid. - pub fn new(ident: impl AsRef + Into) -> Option { - if is_ident(ident.as_ref()) { - Some(Self(ident.into())) - } else { - None - } - } - - /// Return a reference to the underlying string. - pub fn as_str(&self) -> &str { - self - } -} - -impl AsRef for Ident { - fn as_ref(&self) -> &str { - self - } -} - -impl Deref for Ident { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0.as_str() - } -} - -impl Debug for Ident { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "`{}`", self.0) - } -} - -/// Whether the string is a valid identifier. -pub fn is_ident(string: &str) -> bool { - fn is_ok(c: char) -> bool { - c == '-' || c == '_' - } - - let mut chars = string.chars(); - if matches!(chars.next(), Some(c) if c.is_xid_start() || is_ok(c)) { - chars.all(|c| c.is_xid_continue() || is_ok(c)) - } else { - false - } -} - -/// A dictionary of expressions. -/// -/// # Example -/// ```typst -/// (false, 12cm, greeting="hi") -/// ``` -pub type DictExpr = Dict>; - -impl DictExpr { - /// Evaluate the dictionary expression to a dictionary value. - pub fn eval<'a>( - &'a self, - ctx: &'a LayoutContext<'a>, - f: &'a mut Feedback, - ) -> DynFuture<'a, DictValue> { - Box::pin(async move { - let mut dict = DictValue::new(); - - for (key, entry) in self.iter() { - let val = entry.val.v.eval(ctx, f).await; - let spanned = val.span_with(entry.val.span); - let entry = SpannedEntry::new(entry.key, spanned); - dict.insert(key, entry); - } - - dict - }) - } -} - -/// An invocation of a function. -#[derive(Debug, Clone, PartialEq)] -pub struct CallExpr { - pub name: Spanned, - pub args: DictExpr, -} - -impl CallExpr { - /// Evaluate the call expression to a value. - pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { - let name = &self.name.v; - let span = self.name.span; - let args = self.args.eval(ctx, f).await; - - if let Some(func) = ctx.scope.func(name) { - let pass = func(span, args, ctx.clone()).await; - f.extend(pass.feedback); - f.decorations.push(Decoration::Resolved.span_with(span)); - pass.output - } else { - if !name.is_empty() { - error!(@f, span, "unknown function"); - f.decorations.push(Decoration::Unresolved.span_with(span)); - } - Value::Dict(args) - } - } + /// The contents of the heading. + pub contents: SynTree, }