summaryrefslogtreecommitdiff
path: root/src/syntax/ast
diff options
context:
space:
mode:
Diffstat (limited to 'src/syntax/ast')
-rw-r--r--src/syntax/ast/expr.rs123
-rw-r--r--src/syntax/ast/lit.rs98
-rw-r--r--src/syntax/ast/mod.rs9
-rw-r--r--src/syntax/ast/tree.rs121
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,
+}