diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-08-16 22:14:27 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-08-16 22:39:21 +0200 |
| commit | 30f16bbf6431ca0c174ca0a1abaa6a13ef50ab06 (patch) | |
| tree | f5a5c0adad15840ebe24b39e77ff467862067c91 /src/syntax | |
| parent | 9f6137d8a829fe8f34554623495fa620252a0184 (diff) | |
Add Value type and replace dyn-nodes with call-exprs 🏗
- In addition to syntax trees there are now `Value`s, which syntax trees can be evaluated into (e.g. the tree is `5+5` and the value is `10`)
- Parsing is completely pure, function calls are not parsed into nodes, but into simple call expressions, which are resolved later
- Functions aren't dynamic nodes anymore, but simply functions which receive their arguments as a table and the layouting context
- Functions may return any `Value`
- Layouting is powered by functions which return the new `Commands` value, which informs the layouting engine what to do
- When a function returns a non-`Commands` value, the layouter simply dumps the value into the document in monospace
Diffstat (limited to 'src/syntax')
| -rw-r--r-- | src/syntax/decoration.rs | 14 | ||||
| -rw-r--r-- | src/syntax/expr.rs | 639 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 47 | ||||
| -rw-r--r-- | src/syntax/parsing.rs | 201 | ||||
| -rw-r--r-- | src/syntax/scope.rs | 44 | ||||
| -rw-r--r-- | src/syntax/span.rs | 8 | ||||
| -rw-r--r-- | src/syntax/tree.rs | 191 |
7 files changed, 227 insertions, 917 deletions
diff --git a/src/syntax/decoration.rs b/src/syntax/decoration.rs index a686596c..fe961be3 100644 --- a/src/syntax/decoration.rs +++ b/src/syntax/decoration.rs @@ -9,18 +9,18 @@ use super::span::SpanVec; pub type Decorations = SpanVec<Decoration>; /// Decorations for semantic syntax highlighting. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] pub enum Decoration { - /// A valid, successfully resolved function name. - ResolvedFunc, - /// An invalid, unresolved function name. - UnresolvedFunc, - /// The key part of a key-value entry in a table. - TableKey, /// Text in italics. Italic, /// Text in bold. Bold, + /// A valid, successfully resolved name. + Resolved, + /// An invalid, unresolved name. + Unresolved, + /// The key part of a key-value entry in a table. + TableKey, } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs deleted file mode 100644 index e92a75b0..00000000 --- a/src/syntax/expr.rs +++ /dev/null @@ -1,639 +0,0 @@ -//! Expressions in function headers. - -use std::fmt::{self, Debug, Formatter}; -use std::str::FromStr; -use std::u8; - -use fontdock::{FontStyle, FontWeight, FontWidth}; - -use crate::layout::{Dir, SpecAlign}; -use crate::length::{Length, ScaleLength}; -use crate::paper::Paper; -use crate::table::{BorrowedKey, Table}; -use crate::Feedback; -use super::parsing::FuncCall; -use super::span::{Span, Spanned}; -use super::tokens::is_identifier; -use super::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 syntax tree containing typesetting content. - Tree(SyntaxTree), - /// A table: `(false, 12cm, greeting="hi")`. - Table(TableExpr), - /// An operation that negates the contained expression. - Neg(Box<Spanned<Expr>>), - /// An operation that adds the contained expressions. - Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>), - /// An operation that subtracts the contained expressions. - Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>), - /// An operation that multiplies the contained expressions. - Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>), - /// An operation that divides the contained expressions. - Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>), - /// A function call: `cmyk(37.7, 0, 3.9, 1.1)`. - Call(FuncCall), -} - -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", - Tree(_) => "syntax tree", - Table(_) => "table", - Neg(_) => "negation", - Add(_, _) => "addition", - Sub(_, _) => "subtraction", - Mul(_, _) => "multiplication", - Div(_, _) => "division", - Call(_) => "function call", - } - } -} - -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), - Tree(t) => t.fmt(f), - Table(t) => t.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), - Call(c) => c.fmt(f), - } - } -} - -/// 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<str> + Into<String>) -> Option<Self> { - if is_identifier(ident.as_ref()) { - Some(Self(ident.into())) - } else { - None - } - } - - /// Return a reference to the underlying string. - pub fn as_str(&self) -> &str { - self.0.as_str() - } -} - -impl Debug for Ident { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "`{}`", self.0) - } -} - -/// An 8-bit RGBA color. -/// -/// # Example -/// ```typst -/// [page: background=#423abaff] -/// ^^^^^^^^ -/// ``` -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct RgbaColor { - /// Red channel. - pub r: u8, - /// Green channel. - pub g: u8, - /// Blue channel. - pub b: u8, - /// Alpha channel. - pub a: u8, - /// This is true if this value was provided as a fail-over by the parser - /// because the user-defined value was invalid. This color may be - /// overwritten if this property is true. - pub healed: bool, -} - -impl RgbaColor { - /// Constructs a new color. - pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a, healed: false } - } - - /// Constructs a new color with the healed property set to true. - pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a, healed: true } - } -} - -impl FromStr for RgbaColor { - type Err = ParseColorError; - - /// Constructs a new color from a hex string like `7a03c2`. Do not specify a - /// leading `#`. - fn from_str(hex_str: &str) -> Result<Self, Self::Err> { - if !hex_str.is_ascii() { - return Err(ParseColorError); - } - - let len = hex_str.len(); - let long = len == 6 || len == 8; - let short = len == 3 || len == 4; - let alpha = len == 4 || len == 8; - - if !long && !short { - return Err(ParseColorError); - } - - let mut values: [u8; 4] = [255; 4]; - - for elem in if alpha { 0..4 } else { 0..3 } { - let item_len = if long { 2 } else { 1 }; - let pos = elem * item_len; - - let item = &hex_str[pos..(pos+item_len)]; - values[elem] = u8::from_str_radix(item, 16) - .map_err(|_| ParseColorError)?; - - if short { - // Duplicate number for shorthand notation, i.e. `a` -> `aa` - values[elem] += values[elem] * 16; - } - } - - Ok(Self::new(values[0], values[1], values[2], values[3])) - } -} - -impl Debug for RgbaColor { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - write!( - f, "rgba({:02}, {:02}, {:02}, {:02})", - self.r, self.g, self.b, self.a, - )?; - } else { - write!( - f, "#{:02x}{:02x}{:02x}{:02x}", - self.r, self.g, self.b, self.a, - )?; - } - if self.healed { - f.write_str(" [healed]")?; - } - Ok(()) - } -} - -/// The error when parsing an `RgbaColor` fails. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct ParseColorError; - -impl std::error::Error for ParseColorError {} - -impl fmt::Display for ParseColorError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("invalid color") - } -} - -/// A table expression. -/// -/// # Example -/// ```typst -/// (false, 12cm, greeting="hi") -/// ``` -pub type TableExpr = Table<TableExprEntry>; - -impl TableExpr { - /// Retrieve and remove the matching value with the lowest number key, - /// skipping and ignoring all non-matching entries with lower keys. - pub fn take<T: TryFromExpr>(&mut self) -> Option<T> { - for (&key, entry) in self.nums() { - let expr = entry.val.as_ref(); - if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) { - self.remove(key); - return Some(val); - } - } - None - } - - /// Retrieve and remove the matching value with the lowest number key, - /// removing and generating errors for all non-matching entries with lower - /// keys. - pub fn expect<T: TryFromExpr>(&mut self, f: &mut Feedback) -> Option<T> { - while let Some((num, _)) = self.first() { - let entry = self.remove(num).unwrap(); - if let Some(val) = T::try_from_expr(entry.val.as_ref(), f) { - return Some(val); - } - } - None - } - - /// Retrieve and remove a matching value associated with the given key if - /// there is any. - /// - /// Generates an error if the key exists but the value does not match. - pub fn take_with_key<'a, K, T>(&mut self, key: K, f: &mut Feedback) -> Option<T> - where - K: Into<BorrowedKey<'a>>, - T: TryFromExpr, - { - self.remove(key).and_then(|entry| { - let expr = entry.val.as_ref(); - T::try_from_expr(expr, f) - }) - } - - /// Retrieve and remove all matching pairs with number keys, skipping and - /// ignoring non-matching entries. - /// - /// The pairs are returned in order of increasing keys. - pub fn take_all_num<'a, T>(&'a mut self) -> impl Iterator<Item = (u64, T)> + 'a - where - T: TryFromExpr, - { - let mut skip = 0; - std::iter::from_fn(move || { - for (&key, entry) in self.nums().skip(skip) { - let expr = entry.val.as_ref(); - if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) { - self.remove(key); - return Some((key, val)); - } - skip += 1; - } - - None - }) - } - - - /// Retrieve and remove all matching values with number keys, skipping and - /// ignoring non-matching entries. - /// - /// The values are returned in order of increasing keys. - pub fn take_all_num_vals<'a, T: 'a>(&'a mut self) -> impl Iterator<Item = T> + 'a - where - T: TryFromExpr, - { - self.take_all_num::<T>().map(|(_, v)| v) - } - - /// Retrieve and remove all matching pairs with string keys, skipping and - /// ignoring non-matching entries. - /// - /// The pairs are returned in order of increasing keys. - pub fn take_all_str<'a, T>(&'a mut self) -> impl Iterator<Item = (String, T)> + 'a - where - T: TryFromExpr, - { - let mut skip = 0; - std::iter::from_fn(move || { - for (key, entry) in self.strs().skip(skip) { - let expr = entry.val.as_ref(); - if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) { - let key = key.clone(); - self.remove(&key); - return Some((key, val)); - } - skip += 1; - } - - None - }) - } - - /// Generated `"unexpected argument"` errors for all remaining entries. - pub fn unexpected(&self, f: &mut Feedback) { - for entry in self.values() { - let span = Span::merge(entry.key, entry.val.span); - error!(@f, span, "unexpected argument"); - } - } -} - -/// An entry in a table expression. -/// -/// Contains the key's span and the value. -#[derive(Clone, PartialEq)] -pub struct TableExprEntry { - pub key: Span, - pub val: Spanned<Expr>, -} - -impl TableExprEntry { - /// Create a new entry. - pub fn new(key: Span, val: Spanned<Expr>) -> Self { - Self { key, val } - } - - /// Create an entry for a positional argument with the same span for key and - /// value. - pub fn val(val: Spanned<Expr>) -> Self { - Self { key: Span::ZERO, val } - } -} - -impl Debug for TableExprEntry { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - f.write_str("key")?; - self.key.fmt(f)?; - f.write_str(" ")?; - } - self.val.fmt(f) - } -} - -/// A trait for converting expressions into specific types. -pub trait TryFromExpr: Sized { - // This trait takes references because we don't want to move the expression - // out of its origin in case this returns `None`. This solution is not - // perfect because we need to do some cloning in the impls for this trait, - // but I haven't got a better solution, for now. - - /// Try to convert an expression into this type. - /// - /// Returns `None` and generates an appropriate error if the expression is - /// not valid for this type. - fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self>; -} - -macro_rules! impl_match { - ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { - impl TryFromExpr for $type { - fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> { - #[allow(unreachable_patterns)] - match expr.v { - $($p => Some($r)),*, - other => { - error!( - @f, expr.span, - "expected {}, found {}", $name, other.name() - ); - None - } - } - } - } - }; -} - -macro_rules! impl_ident { - ($type:ty, $name:expr, $parse:expr) => { - impl TryFromExpr for $type { - fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> { - if let Expr::Ident(ident) = expr.v { - let val = $parse(ident.as_str()); - if val.is_none() { - error!(@f, expr.span, "invalid {}", $name); - } - val - } else { - error!( - @f, expr.span, - "expected {}, found {}", $name, expr.v.name() - ); - None - } - } - } - }; -} - -impl<T: TryFromExpr> TryFromExpr for Spanned<T> { - fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> { - let span = expr.span; - T::try_from_expr(expr, f).map(|v| Spanned { v, span }) - } -} - -impl_match!(Expr, "expression", e => e.clone()); -impl_match!(Ident, "identifier", Expr::Ident(i) => i.clone()); -impl_match!(String, "string", Expr::Str(s) => s.clone()); -impl_match!(bool, "bool", Expr::Bool(b) => b.clone()); -impl_match!(f64, "number", Expr::Number(n) => n.clone()); -impl_match!(Length, "length", Expr::Length(l) => l.clone()); -impl_match!(SyntaxTree, "tree", Expr::Tree(t) => t.clone()); -impl_match!(TableExpr, "table", Expr::Table(t) => t.clone()); -impl_match!(ScaleLength, "number or length", - &Expr::Length(length) => ScaleLength::Absolute(length), - &Expr::Number(scale) => ScaleLength::Scaled(scale), -); - -/// A value type that matches identifiers and strings and implements -/// `Into<String>`. -pub struct StringLike(pub String); - -impl From<StringLike> for String { - fn from(like: StringLike) -> String { - like.0 - } -} - -impl_match!(StringLike, "identifier or string", - Expr::Ident(Ident(s)) => StringLike(s.clone()), - Expr::Str(s) => StringLike(s.clone()), -); - -impl_ident!(Dir, "direction", |s| match s { - "ltr" => Some(Self::LTR), - "rtl" => Some(Self::RTL), - "ttb" => Some(Self::TTB), - "btt" => Some(Self::BTT), - _ => None, -}); - -impl_ident!(SpecAlign, "alignment", |s| match s { - "left" => Some(Self::Left), - "right" => Some(Self::Right), - "top" => Some(Self::Top), - "bottom" => Some(Self::Bottom), - "center" => Some(Self::Center), - _ => None, -}); - -impl_ident!(FontStyle, "font style", FontStyle::from_name); -impl_ident!(Paper, "paper", Paper::from_name); - -impl TryFromExpr for FontWeight { - fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> { - match expr.v { - &Expr::Number(weight) => { - const MIN: u16 = 100; - const MAX: u16 = 900; - - Some(Self(if weight < MIN as f64 { - error!(@f, expr.span, "the minimum font weight is {}", MIN); - MIN - } else if weight > MAX as f64 { - error!(@f, expr.span, "the maximum font weight is {}", MAX); - MAX - } else { - weight.round() as u16 - })) - } - Expr::Ident(ident) => { - let weight = Self::from_name(ident.as_str()); - if weight.is_none() { - error!(@f, expr.span, "invalid font weight"); - } - weight - } - other => { - error!( - @f, expr.span, - "expected font weight (name or number), found {}", - other.name(), - ); - None - } - } - } -} - -impl TryFromExpr for FontWidth { - fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> { - match expr.v { - &Expr::Number(width) => { - const MIN: u16 = 1; - const MAX: u16 = 9; - - Self::new(if width < MIN as f64 { - error!(@f, expr.span, "the minimum font width is {}", MIN); - MIN - } else if width > MAX as f64 { - error!(@f, expr.span, "the maximum font width is {}", MAX); - MAX - } else { - width.round() as u16 - }) - } - Expr::Ident(ident) => { - let width = Self::from_name(ident.as_str()); - if width.is_none() { - error!(@f, expr.span, "invalid font width"); - } - width - } - other => { - error!( - @f, expr.span, - "expected font width (name or number), found {}", - other.name(), - ); - None - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_color_strings() { - fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) { - assert_eq!( - RgbaColor::from_str(hex), - Ok(RgbaColor::new(r, g, b, a)), - ); - } - - test("f61243ff", 0xf6, 0x12, 0x43, 0xff); - test("b3d8b3", 0xb3, 0xd8, 0xb3, 0xff); - test("fCd2a9AD", 0xfc, 0xd2, 0xa9, 0xad); - test("233", 0x22, 0x33, 0x33, 0xff); - test("111b", 0x11, 0x11, 0x11, 0xbb); - } - - fn entry(expr: Expr) -> TableExprEntry { - TableExprEntry { - key: Span::ZERO, - val: Spanned::zero(expr), - } - } - - #[test] - fn test_table_take_removes_correct_entry() { - let mut table = TableExpr::new(); - table.insert(1, entry(Expr::Bool(false))); - table.insert(2, entry(Expr::Str("hi".to_string()))); - assert_eq!(table.take::<String>(), Some("hi".to_string())); - assert_eq!(table.len(), 1); - assert_eq!(table.take::<bool>(), Some(false)); - assert!(table.is_empty()); - } - - #[test] - fn test_table_expect_errors_about_previous_entries() { - let mut f = Feedback::new(); - let mut table = TableExpr::new(); - table.insert(1, entry(Expr::Bool(false))); - table.insert(3, entry(Expr::Str("hi".to_string()))); - table.insert(5, entry(Expr::Bool(true))); - assert_eq!(table.expect::<String>(&mut f), Some("hi".to_string())); - assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected string, found bool")]); - assert_eq!(table.len(), 1); - } - - #[test] - fn test_table_take_with_key_removes_the_entry() { - let mut f = Feedback::new(); - let mut table = TableExpr::new(); - table.insert(1, entry(Expr::Bool(false))); - table.insert("hi", entry(Expr::Bool(true))); - assert_eq!(table.take_with_key::<_, bool>(1, &mut f), Some(false)); - assert_eq!(table.take_with_key::<_, f64>("hi", &mut f), None); - assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected number, found bool")]); - assert!(table.is_empty()); - } - - #[test] - fn test_table_take_all_removes_the_correct_entries() { - let mut table = TableExpr::new(); - table.insert(1, entry(Expr::Bool(false))); - table.insert(3, entry(Expr::Number(0.0))); - table.insert(7, entry(Expr::Bool(true))); - assert_eq!( - table.take_all_num::<bool>().collect::<Vec<_>>(), - [(1, false), (7, true)], - ); - assert_eq!(table.len(), 1); - assert_eq!(table[3].val.v, Expr::Number(0.0)); - } -} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index fdc105a3..596291a5 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -1,17 +1,44 @@ //! Syntax trees, parsing and tokenization. pub mod decoration; -pub mod expr; pub mod parsing; -pub mod scope; pub mod span; pub mod tokens; pub mod tree; +use std::fmt::{self, Debug, Formatter}; +use tokens::is_identifier; + +/// 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<str> + Into<String>) -> Option<Self> { + if is_identifier(ident.as_ref()) { + Some(Self(ident.into())) + } else { + None + } + } + + /// Return a reference to the underlying string. + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl Debug for Ident { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "`{}`", self.0) + } +} + #[cfg(test)] mod tests { use std::fmt::Debug; - use crate::func::prelude::*; + use crate::prelude::*; use super::span; /// Assert that expected and found are equal, printing both and panicking @@ -49,18 +76,4 @@ mod tests { Spanned::zero(t) } } - - pub fn debug_func(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> { - Pass::node(DebugNode(call), Feedback::new()) - } - - #[derive(Debug, Clone, PartialEq)] - pub struct DebugNode(pub FuncCall); - - #[async_trait(?Send)] - impl Layout for DebugNode { - async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> { - unimplemented!() - } - } } diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 88c930cb..8ed778e1 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -3,31 +3,13 @@ use std::str::FromStr; use crate::{Feedback, Pass}; +use crate::color::RgbaColor; +use crate::compute::table::SpannedEntry; use super::decoration::Decoration; -use super::expr::*; -use super::scope::Scope; use super::span::{Pos, Span, Spanned}; use super::tokens::{is_newline_char, Token, TokenMode, Tokens}; -use super::tree::{SyntaxNode, SyntaxTree}; - -/// A function which parses a function call into a dynamic node. -pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<SyntaxNode>; - -/// An invocation of a function. -#[derive(Debug, Clone, PartialEq)] -pub struct FuncCall { - pub name: Spanned<Ident>, - pub args: TableExpr, -} - -/// The state which can influence how a string of source code is parsed. -/// -/// Parsing is pure - when passed in the same state and source code, the output -/// must be the same. -pub struct ParseState { - /// The scope containing all function definitions. - pub scope: Scope, -} +use super::tree::{CallExpr, Expr, SyntaxNode, SyntaxTree, TableExpr}; +use super::Ident; /// Parse a string of source code. /// @@ -35,7 +17,7 @@ pub struct ParseState { /// `offset` position. This is used to make spans of a function body relative to /// the start of the function as a whole as opposed to the start of the /// function's body. -pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> { +pub fn parse(src: &str, offset: Pos) -> Pass<SyntaxTree> { let mut tree = SyntaxTree::new(); let mut par = SyntaxTree::new(); let mut feedback = Feedback::new(); @@ -58,12 +40,12 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> { } Token::Function { header, body, terminated } => { - let parsed = FuncParser::new(header, body, state).parse(); + let parsed = FuncParser::new(header, body).parse(); feedback.extend_offset(parsed.feedback, span.start); if !terminated { error!(@feedback, Span::at(span.end), "expected closing bracket"); } - parsed.output + SyntaxNode::Call(parsed.output) } Token::Star => SyntaxNode::ToggleBolder, @@ -97,8 +79,6 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> { } struct FuncParser<'s> { - state: &'s ParseState, - /// The tokens inside the header. tokens: Tokens<'s>, peeked: Option<Option<Spanned<Token<'s>>>>, body: Option<Spanned<&'s str>>, @@ -106,13 +86,8 @@ struct FuncParser<'s> { } impl<'s> FuncParser<'s> { - fn new( - header: &'s str, - body: Option<Spanned<&'s str>>, - state: &'s ParseState, - ) -> Self { + fn new(header: &'s str, body: Option<Spanned<&'s str>>) -> Self { Self { - state, // Start at column 1 because the opening bracket is also part of // the function, but not part of the `header` string. tokens: Tokens::new(header, Pos::new(0, 1), TokenMode::Header), @@ -122,65 +97,17 @@ impl<'s> FuncParser<'s> { } } - fn parse(mut self) -> Pass<SyntaxNode> { - let (parser, mut call) = if let Some(call) = self.parse_func_header() { - let name = call.name.v.as_str(); - let (parser, deco) = match self.state.scope.get_parser(name) { - // The function exists in the scope. - Some(parser) => (parser, Decoration::ResolvedFunc), - - // The function does not exist in the scope. The parser that is - // returned here is a fallback parser which exists to make sure - // the content of the function is not totally dropped (on a best - // effort basis). - None => { - error!(@self.feedback, call.name.span, "unknown function"); - let parser = self.state.scope.get_fallback_parser(); - (parser, Decoration::UnresolvedFunc) - } - }; - - self.feedback.decorations.push(Spanned::new(deco, call.name.span)); - (parser, call) - } else { - // Parse the call with the fallback parser even when the header is - // completely unparsable. - let parser = self.state.scope.get_fallback_parser(); - let call = FuncCall { - name: Spanned::new(Ident(String::new()), Span::ZERO), - args: TableExpr::new(), - }; - (parser, call) - }; - - if let Some(body) = self.body { - call.args.push(TableExprEntry { - key: Span::ZERO, - val: body.map(|src| { - let parsed = parse(src, body.span.start, &self.state); - self.feedback.extend(parsed.feedback); - Expr::Tree(parsed.output) - }), - }); - } - - let parsed = parser(call, self.state); - self.feedback.extend(parsed.feedback); - - Pass::new(parsed.output, self.feedback) - } - - fn parse_func_header(&mut self) -> Option<FuncCall> { + fn parse(mut self) -> Pass<CallExpr> { let after_bracket = self.pos(); self.skip_white(); - let name = try_opt_or!(self.parse_ident(), { + let name = self.parse_ident().unwrap_or_else(|| { self.expected_found_or_at("function name", after_bracket); - return None; + Spanned::zero(Ident(String::new())) }); self.skip_white(); - let args = match self.eat().map(Spanned::value) { + let mut args = match self.eat().map(Spanned::value) { Some(Token::Colon) => self.parse_table(false).0.v, Some(_) => { self.expected_at("colon", name.span.end); @@ -189,7 +116,15 @@ impl<'s> FuncParser<'s> { None => TableExpr::new(), }; - Some(FuncCall { name, args }) + if let Some(body) = self.body { + args.push(SpannedEntry::val(body.map(|src| { + let parsed = parse(src, body.span.start); + self.feedback.extend(parsed.feedback); + Expr::Tree(parsed.output) + }))); + } + + Pass::new(CallExpr { name, args }, self.feedback) } } @@ -325,14 +260,20 @@ impl FuncParser<'_> { } } - fn parse_func_call(&mut self, name: Spanned<Ident>) -> Spanned<FuncCall> { + fn parse_func_call(&mut self, name: Spanned<Ident>) -> Spanned<CallExpr> { let args = self.parse_table(true).0; let span = Span::merge(name.span, args.span); - Spanned::new(FuncCall { name, args: args.v }, span) + Spanned::new(CallExpr { name, args: args.v }, span) } - /// The boolean tells you whether the table can be coerced into an expression - /// (this is the case when it's length 1 and has no trailing comma). + /// Set `parens` to true, when this should expect an opening paren and stop + /// at the balanced closing paren (this is the case for normal tables and + /// round-paren function calls). Set it to false, when this is used to parse + /// the top-level function arguments. + /// + /// The returned boolean tells you whether the table can be coerced into an + /// expression (this is the case when it's length 1 and has no trailing + /// comma). fn parse_table(&mut self, parens: bool) -> (Spanned<TableExpr>, bool) { let start = self.pos(); if parens { @@ -369,19 +310,19 @@ impl FuncParser<'_> { coercable = false; behind_arg = val.span.end; - table.insert(key.v.0, TableExprEntry::new(key.span, val)); + table.insert(key.v.0, SpannedEntry::new(key.span, val)); } else if self.check(Token::LeftParen) { let call = self.parse_func_call(ident); let expr = call.map(|call| Expr::Call(call)); behind_arg = expr.span.end; - table.push(TableExprEntry::val(expr)); + table.push(SpannedEntry::val(expr)); } else { let expr = ident.map(|id| Expr::Ident(id)); behind_arg = expr.span.end; - table.push(TableExprEntry::val(expr)); + table.push(SpannedEntry::val(expr)); } } else { // It's a positional argument. @@ -390,7 +331,7 @@ impl FuncParser<'_> { continue; }); behind_arg = expr.span.end; - table.push(TableExprEntry::val(expr)); + table.push(SpannedEntry::val(expr)); } self.skip_white(); @@ -593,7 +534,7 @@ mod tests { } macro_rules! F { - ($($tts:tt)*) => { SyntaxNode::boxed(DebugNode(Call!(@$($tts)*))) } + ($($tts:tt)*) => { SyntaxNode::Call(Call!(@$($tts)*)) } } // ------------------------ Construct Expressions ----------------------- // @@ -603,24 +544,17 @@ mod tests { fn Id(ident: &str) -> Expr { Expr::Ident(Ident(ident.to_string())) } fn Str(string: &str) -> Expr { Expr::Str(string.to_string()) } - macro_rules! Tree { - (@$($node:expr),* $(,)?) => { - vec![$(Into::<Spanned<SyntaxNode>>::into($node)),*] - }; - ($($tts:tt)*) => { Expr::Tree(Tree![@$($tts)*]) }; - } - macro_rules! Table { (@table=$table:expr,) => {}; (@table=$table:expr, $key:expr => $value:expr $(, $($tts:tt)*)?) => {{ let key = Into::<Spanned<&str>>::into($key); let val = Into::<Spanned<Expr>>::into($value); - $table.insert(key.v, TableExprEntry::new(key.span, val)); + $table.insert(key.v, SpannedEntry::new(key.span, val)); Table![@table=$table, $($($tts)*)?]; }}; (@table=$table:expr, $value:expr $(, $($tts:tt)*)?) => { let val = Into::<Spanned<Expr>>::into($value); - $table.push(TableExprEntry::val(val)); + $table.push(SpannedEntry::val(val)); Table![@table=$table, $($($tts)*)?]; }; (@$($tts:tt)*) => {{ @@ -632,6 +566,24 @@ mod tests { ($($tts:tt)*) => { Expr::Table(Table![@$($tts)*]) }; } + macro_rules! Tree { + (@$($node:expr),* $(,)?) => { + vec![$(Into::<Spanned<SyntaxNode>>::into($node)),*] + }; + ($($tts:tt)*) => { Expr::Tree(Tree![@$($tts)*]) }; + } + + macro_rules! Call { + (@$name:expr $(; $($tts:tt)*)?) => {{ + let name = Into::<Spanned<&str>>::into($name); + CallExpr { + name: name.map(|n| Ident(n.to_string())), + args: Table![@$($($tts)*)?], + } + }}; + ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) }; + } + fn Neg<T: Into<Spanned<Expr>>>(e1: T) -> Expr { Expr::Neg(Box::new(e1.into())) } @@ -648,17 +600,6 @@ mod tests { Expr::Div(Box::new(e1.into()), Box::new(e2.into())) } - macro_rules! Call { - (@$name:expr $(; $($tts:tt)*)?) => {{ - let name = Into::<Spanned<&str>>::into($name); - FuncCall { - name: name.map(|n| Ident(n.to_string())), - args: Table![@$($($tts)*)?], - } - }}; - ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) }; - } - // ----------------------------- Test Macros ---------------------------- // // Test syntax trees with or without spans. @@ -667,7 +608,7 @@ mod tests { macro_rules! test { (@spans=$spans:expr, $src:expr => $($tts:tt)*) => { let exp = Tree![@$($tts)*]; - let pass = parse_default($src); + let pass = parse($src, Pos::ZERO); check($src, exp, pass.output, $spans); }; } @@ -683,7 +624,7 @@ mod tests { macro_rules! e { ($src:expr => $($tts:tt)*) => { let exp = vec![$($tts)*]; - let pass = parse_default($src); + let pass = parse($src, Pos::ZERO); let found = pass.feedback.diagnostics.iter() .map(|s| s.as_ref().map(|e| e.message.as_str())) .collect::<Vec<_>>(); @@ -695,20 +636,11 @@ mod tests { macro_rules! d { ($src:expr => $($tts:tt)*) => { let exp = vec![$($tts)*]; - let pass = parse_default($src); + let pass = parse($src, Pos::ZERO); check($src, exp, pass.feedback.decorations, true); }; } - fn parse_default(src: &str) -> Pass<SyntaxTree> { - let mut scope = Scope::new(Box::new(debug_func)); - scope.insert("box", Box::new(debug_func)); - scope.insert("val", Box::new(debug_func)); - scope.insert("f", Box::new(debug_func)); - let state = ParseState { scope }; - parse(src, Pos::ZERO, &state) - } - // -------------------------------- Tests ------------------------------- // #[test] @@ -797,15 +729,9 @@ mod tests { e!("[\"]" => s(0,1, 0,3, "expected function name, found string"), s(0,3, 0,3, "expected closing bracket")); - // An unknown name. - t!("[hi]" => P![F!("hi")]); - e!("[hi]" => s(0,1, 0,3, "unknown function")); - d!("[hi]" => s(0,1, 0,3, UnresolvedFunc)); - // A valid name. - t!("[f]" => P![F!("f")]); + t!("[hi]" => P![F!("hi")]); t!("[ f]" => P![F!("f")]); - d!("[ f]" => s(0,3, 0,4, ResolvedFunc)); // An invalid name. e!("[12]" => s(0,1, 0,3, "expected function name, found number")); @@ -821,7 +747,6 @@ mod tests { t!("[val=]" => P![F!("val")]); e!("[val=]" => s(0,4, 0,4, "expected colon")); e!("[val/🌎:$]" => s(0,4, 0,4, "expected colon")); - d!("[val=]" => s(0,1, 0,4, ResolvedFunc)); // String in invalid header without colon still parsed as string // Note: No "expected quote" error because not even the string was @@ -939,9 +864,9 @@ mod tests { v!("(1, key=\"value\")" => Table![Num(1.0), "key" => Str("value")]); // Decorations. - d!("[val: key=hi]" => s(0,6, 0,9, TableKey), s(0,1, 0,4, ResolvedFunc)); - d!("[val: (key=hi)]" => s(0,7, 0,10, TableKey), s(0,1, 0,4, ResolvedFunc)); - d!("[val: f(key=hi)]" => s(0,8, 0,11, TableKey), s(0,1, 0,4, ResolvedFunc)); + d!("[val: key=hi]" => s(0,6, 0,9, TableKey)); + d!("[val: (key=hi)]" => s(0,7, 0,10, TableKey)); + d!("[val: f(key=hi)]" => s(0,8, 0,11, TableKey)); // Spanned with spacing around keyword arguments. ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0,0, 4,2, P![ diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs deleted file mode 100644 index c17ff64d..00000000 --- a/src/syntax/scope.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Mapping of function names to function parsers. - -use std::collections::HashMap; -use std::fmt::{self, Debug, Formatter}; - -use super::parsing::CallParser; - -/// A map from identifiers to function parsers. -pub struct Scope { - parsers: HashMap<String, Box<CallParser>>, - fallback: Box<CallParser>, -} - -impl Scope { - /// Create a new empty scope with a fallback parser that is invoked when no - /// match is found. - pub fn new(fallback: Box<CallParser>) -> Self { - Self { - parsers: HashMap::new(), - fallback, - } - } - - /// Associate the given function name with a dynamic node type. - pub fn insert(&mut self, name: impl Into<String>, parser: Box<CallParser>) { - self.parsers.insert(name.into(), parser); - } - - /// Return the parser with the given name if there is one. - pub fn get_parser(&self, name: &str) -> Option<&CallParser> { - self.parsers.get(name).map(AsRef::as_ref) - } - - /// Return the fallback parser. - pub fn get_fallback_parser(&self) -> &CallParser { - &*self.fallback - } -} - -impl Debug for Scope { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_set().entries(self.parsers.keys()).finish() - } -} diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 923d13c2..89f773c7 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -39,13 +39,11 @@ impl<T> Offset for SpanVec<T> { } /// A value with the span it corresponds to in the source code. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] pub struct Spanned<T> { - /// The value. - pub v: T, - /// The corresponding span. pub span: Span, + pub v: T, } impl<T> Spanned<T> { @@ -99,7 +97,7 @@ impl<T: Debug> Debug for Spanned<T> { } /// Locates a slice of source code. -#[derive(Copy, Clone, Hash)] +#[derive(Copy, Clone, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] pub struct Span { /// The inclusive start position. diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs index 4ce39cd4..e7a1eaf1 100644 --- a/src/syntax/tree.rs +++ b/src/syntax/tree.rs @@ -1,17 +1,20 @@ //! The syntax tree. -use std::any::Any; -use std::fmt::Debug; +use std::fmt::{self, Debug, Formatter}; -use crate::layout::Layout; -use super::span::SpanVec; +use crate::color::RgbaColor; +use crate::compute::table::{SpannedEntry, Table}; +use crate::compute::value::{TableValue, Value}; +use crate::length::Length; +use super::span::{Spanned, SpanVec}; +use super::Ident; /// A collection of nodes which form a tree together with the nodes' children. pub type SyntaxTree = SpanVec<SyntaxNode>; /// A syntax node, which encompasses a single logical entity of parsed source /// code. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum SyntaxNode { /// Whitespace containing less than two newlines. Spacing, @@ -27,84 +30,138 @@ pub enum SyntaxNode { Raw(Vec<String>), /// A paragraph of child nodes. Par(SyntaxTree), - /// A dynamic node, created through function invocations in source code. - Dyn(Box<dyn DynamicNode>), + /// A function call. + Call(CallExpr), } -impl SyntaxNode { - /// Create a `Dyn` variant from an unboxed dynamic node. - pub fn boxed<T: DynamicNode + 'static>(node: T) -> SyntaxNode { - SyntaxNode::Dyn(Box::new(node)) - } +/// 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 table expression: `(false, 12cm, greeting="hi")`. + Table(TableExpr), + /// 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<Spanned<Expr>>), + /// An operation that adds the contained expressions. + Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>), + /// An operation that subtracts the contained expressions. + Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>), + /// An operation that multiplies the contained expressions. + Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>), + /// An operation that divides the contained expressions. + Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>), } -impl PartialEq for SyntaxNode { - fn eq(&self, other: &SyntaxNode) -> bool { - use SyntaxNode::*; - match (self, other) { - (Spacing, Spacing) => true, - (Linebreak, Linebreak) => true, - (ToggleItalic, ToggleItalic) => true, - (ToggleBolder, ToggleBolder) => true, - (Text(a), Text(b)) => a == b, - (Raw(a), Raw(b)) => a == b, - (Par(a), Par(b)) => a == b, - (Dyn(a), Dyn(b)) => a == b, - _ => false, +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", + Table(_) => "table", + Tree(_) => "syntax tree", + Call(_) => "function call", + Neg(_) => "negation", + Add(_, _) => "addition", + Sub(_, _) => "subtraction", + Mul(_, _) => "multiplication", + Div(_, _) => "division", } } -} - -/// Dynamic syntax nodes. -/// -/// *Note*: This is automatically implemented for all types which are -/// `Debug + Clone + PartialEq`, `Layout` and `'static`. -pub trait DynamicNode: Debug + Layout { - /// Convert into a `dyn Any`. - fn as_any(&self) -> &dyn Any; - - /// Check for equality with another dynamic node. - fn dyn_eq(&self, other: &dyn DynamicNode) -> bool; - /// Clone into a boxed node trait object. - fn box_clone(&self) -> Box<dyn DynamicNode>; -} - -impl dyn DynamicNode { - /// Downcast this dynamic node to a concrete node. - pub fn downcast<T: DynamicNode + 'static>(&self) -> Option<&T> { - self.as_any().downcast_ref::<T>() + /// Evaluate the expression to a value. + pub fn eval(&self) -> 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), + Table(t) => Value::Table(t.eval()), + Tree(t) => Value::Tree(t.clone()), + Call(_) => todo!("eval call"), + Neg(_) => todo!("eval neg"), + Add(_, _) => todo!("eval add"), + Sub(_, _) => todo!("eval sub"), + Mul(_, _) => todo!("eval mul"), + Div(_, _) => todo!("eval div"), + } } } -impl PartialEq for dyn DynamicNode { - fn eq(&self, other: &Self) -> bool { - self.dyn_eq(other) +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), + Table(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), + } } } -impl Clone for Box<dyn DynamicNode> { - fn clone(&self) -> Self { - self.box_clone() - } -} +/// A table of expressions. +/// +/// # Example +/// ```typst +/// (false, 12cm, greeting="hi") +/// ``` +pub type TableExpr = Table<SpannedEntry<Expr>>; -impl<T> DynamicNode for T -where - T: Debug + PartialEq + Clone + Layout + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } +impl TableExpr { + /// Evaluate the table expression to a table value. + pub fn eval(&self) -> TableValue { + let mut table = TableValue::new(); - fn dyn_eq(&self, other: &dyn DynamicNode) -> bool { - match other.as_any().downcast_ref::<Self>() { - Some(other) => self == other, - None => false, + for (&key, entry) in self.nums() { + table.insert(key, entry.as_ref().map(|val| val.eval())); } - } - fn box_clone(&self) -> Box<dyn DynamicNode> { - Box::new(self.clone()) + for (key, entry) in self.strs() { + table.insert(key.clone(), entry.as_ref().map(|val| val.eval())); + } + + table } } + +/// An invocation of a function. +#[derive(Debug, Clone, PartialEq)] +pub struct CallExpr { + pub name: Spanned<Ident>, + pub args: TableExpr, +} |
