diff options
Diffstat (limited to 'src/syntax/ast')
| -rw-r--r-- | src/syntax/ast/expr.rs | 123 | ||||
| -rw-r--r-- | src/syntax/ast/lit.rs | 98 | ||||
| -rw-r--r-- | src/syntax/ast/mod.rs | 9 | ||||
| -rw-r--r-- | src/syntax/ast/tree.rs | 121 |
4 files changed, 351 insertions, 0 deletions
diff --git a/src/syntax/ast/expr.rs b/src/syntax/ast/expr.rs new file mode 100644 index 00000000..c07c6216 --- /dev/null +++ b/src/syntax/ast/expr.rs @@ -0,0 +1,123 @@ +//! Expressions. + +use crate::eval::Value; +use crate::layout::LayoutContext; +use crate::syntax::{Decoration, Ident, Lit, LitDict, SpanWith, Spanned}; +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<UnOp>, + /// The expression to operator on: `x`. + pub expr: Spanned<Box<Expr>>, +} + +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, Copy, Clone, Eq, 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<Box<Expr>>, + /// The operator: `+`. + pub op: Spanned<BinOp>, + /// The right-hand side of the operation: `b`. + pub rhs: Spanned<Box<Expr>>, +} + +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, Copy, Clone, Eq, 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<Ident>, + /// 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/ast/lit.rs b/src/syntax/ast/lit.rs new file mode 100644 index 00000000..bbdd0c81 --- /dev/null +++ b/src/syntax/ast/lit.rs @@ -0,0 +1,98 @@ +//! Literals. + +use crate::color::RgbaColor; +use crate::eval::{DictKey, DictValue, SpannedEntry, Value}; +use crate::layout::LayoutContext; +use crate::length::Length; +use crate::syntax::{Expr, Ident, SpanWith, Spanned, SynTree}; +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, Clone, PartialEq)] +pub struct LitDict(pub Vec<LitDictEntry>); + +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.expr.v.eval(ctx, f).await; + let spanned = val.span_with(entry.expr.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<Spanned<DictKey>>, + /// The value of the entry: `"hi"`. + pub expr: Spanned<Expr>, +} diff --git a/src/syntax/ast/mod.rs b/src/syntax/ast/mod.rs new file mode 100644 index 00000000..56ae4134 --- /dev/null +++ b/src/syntax/ast/mod.rs @@ -0,0 +1,9 @@ +//! Abstract syntax tree definition. + +mod expr; +mod lit; +mod tree; + +pub use expr::*; +pub use lit::*; +pub use tree::*; diff --git a/src/syntax/ast/tree.rs b/src/syntax/ast/tree.rs new file mode 100644 index 00000000..03aa3439 --- /dev/null +++ b/src/syntax/ast/tree.rs @@ -0,0 +1,121 @@ +//! The syntax tree. + +use crate::syntax::{Expr, Ident, SpanVec, Spanned}; + +/// A collection of nodes which form a tree together with the nodes' children. +pub type SynTree = SpanVec<SynNode>; + +/// A syntax node, which encompasses a single logical entity of parsed source +/// code. +#[derive(Debug, Clone, PartialEq)] +pub enum SynNode { + /// Whitespace containing less than two newlines. + Space, + /// Plain text. + Text(String), + + /// A forced line break. + Linebreak, + /// A paragraph break. + Parbreak, + /// Italics were enabled / disabled. + ToggleItalic, + /// Bolder was enabled / disabled. + ToggleBolder, + + /// A section heading. + Heading(NodeHeading), + /// An optionally syntax-highlighted raw block. + Raw(NodeRaw), + + /// An expression. + Expr(Expr), +} + +/// A section heading. +#[derive(Debug, Clone, PartialEq)] +pub struct NodeHeading { + /// The section depth (how many hashtags minus 1). + pub level: Spanned<u8>, + /// The contents of the heading. + pub contents: SynTree, +} + +/// A raw block, rendered in monospace with optional syntax highlighting. +/// +/// Raw blocks start with an arbitrary number of backticks and end with the same +/// number of backticks. If you want to include a sequence of backticks in a raw +/// block, simply surround the block with more backticks. +/// +/// When using at least two backticks, an optional language tag may follow +/// directly after the backticks. This tag defines which language to +/// syntax-highlight the text in. Apart from the language tag and some +/// whitespace trimming discussed below, everything inside a raw block is +/// rendered verbatim, in particular, there are no escape sequences. +/// +/// # Examples +/// - Raw text is surrounded by backticks. +/// ```typst +/// `raw` +/// ``` +/// - An optional language tag may follow directly at the start when the block +/// is surrounded by at least two backticks. +/// ```typst +/// ``rust println!("hello!")``; +/// ``` +/// - Blocks can span multiple lines. Two backticks suffice to be able to +/// specify the language tag, but three are fine, too. +/// ```typst +/// ``rust +/// loop { +/// find_yak().shave(); +/// } +/// `` +/// ``` +/// - Start with a space to omit the language tag (the space will be trimmed +/// from the output) and use more backticks to allow backticks in the raw +/// text. +/// `````typst +/// ```` This contains ```backticks``` and has no leading & trailing spaces. ```` +/// ````` +/// +/// # Trimming +/// If we would always render the raw text between the backticks exactly as +/// given, a few things would become problematic or even impossible: +/// - Typical multiline code blocks (like in the example above) would have an +/// additional newline before and after the code. +/// - Raw text wrapped in more than one backtick could not exist without +/// leading whitespace since the first word would be interpreted as a +/// language tag. +/// - A single backtick without surrounding spaces could not exist as raw text +/// since it would be interpreted as belonging to the opening or closing +/// backticks. +/// +/// To fix these problems, we trim text in multi-backtick blocks as follows: +/// - We trim a single space or a sequence of whitespace followed by a newline +/// at the start. +/// - We trim a single space or a newline followed by a sequence of whitespace +/// at the end. +/// +/// With these rules, a single raw backtick can be produced by the sequence +/// ``` `` ` `` ```, ``` `` unhighlighted text `` ``` has no surrounding +/// spaces and multiline code blocks don't have extra empty lines. Note that +/// you can always force leading or trailing whitespace simply by adding more +/// spaces. +#[derive(Debug, Clone, PartialEq)] +pub struct NodeRaw { + /// An optional identifier specifying the language to syntax-highlight in. + pub lang: Option<Ident>, + /// The lines of raw text, determined as the raw string between the + /// backticks trimmed according to the above rules and split at newlines. + pub lines: Vec<String>, + /// Whether the element can be layouted inline. + /// + /// - When true, it will be layouted integrated within the surrounding + /// paragraph. + /// - When false, it will be separated into its own paragraph. + /// + /// Single-backtick blocks are always inline-level. Multi-backtick blocks + /// are inline-level when they contain no newlines. + pub inline: bool, +} |
