diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-10-01 15:03:37 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-10-01 15:03:37 +0200 |
| commit | 7fcad452b87c8bd31a9b7dfba78c1b1a92d33dd9 (patch) | |
| tree | c1e82792456be54fd41e7b143be302dcd874e30b /src/syntax | |
| parent | aafd3c95cacd829b647cfab1533de5d4833b9a04 (diff) | |
Reorganize ast types 🏕
Diffstat (limited to 'src/syntax')
| -rw-r--r-- | src/syntax/expr.rs | 124 | ||||
| -rw-r--r-- | src/syntax/ident.rs | 58 | ||||
| -rw-r--r-- | src/syntax/lit.rs | 99 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 14 | ||||
| -rw-r--r-- | src/syntax/span.rs | 4 | ||||
| -rw-r--r-- | src/syntax/tree.rs | 249 |
6 files changed, 310 insertions, 238 deletions
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs new file mode 100644 index 00000000..817b3d6e --- /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<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, 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<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, 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<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/ident.rs b/src/syntax/ident.rs new file mode 100644 index 00000000..5ab7c73c --- /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<str> + Into<String>) -> Option<Self> { + 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<str> 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 00000000..e9807a17 --- /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<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.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<Spanned<DictKey>>, + /// The value of the entry: `"hi"`. + pub value: Spanned<Expr>, +} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 0809d33f..f4472df5 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 eb029479..62929706 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -42,8 +42,10 @@ impl<T> Offset for SpanVec<T> { #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] pub struct Spanned<T> { - pub span: Span, + /// The spanned value. pub v: T, + /// The location in source code of the value. + pub span: Span, } impl<T> Spanned<T> { diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs index c4ce3ad2..80bca399 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<SyntaxNode>; +pub type SynTree = SpanVec<SynNode>; /// 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<Ident>, /// 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<u8>, - 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<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 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<str> + Into<String>) -> Option<Self> { - 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<str> 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<SpannedEntry<Expr>>; - -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<Ident>, - 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, } |
