mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Reorganize ast types 🏕
This commit is contained in:
parent
aafd3c95ca
commit
7fcad452b8
@ -52,38 +52,38 @@ impl<V> Dict<V> {
|
||||
/// Get a reference to the value with the given key.
|
||||
pub fn get<'a, K>(&self, key: K) -> Option<&V>
|
||||
where
|
||||
K: Into<BorrowedKey<'a>>,
|
||||
K: Into<RefKey<'a>>,
|
||||
{
|
||||
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<BorrowedKey<'a>>,
|
||||
K: Into<RefKey<'a>>,
|
||||
{
|
||||
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<K>(&mut self, key: K, value: V)
|
||||
where
|
||||
K: Into<OwnedKey>,
|
||||
K: Into<DictKey>,
|
||||
{
|
||||
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<V> Dict<V> {
|
||||
/// Remove the value with the given key from the dictionary.
|
||||
pub fn remove<'a, K>(&mut self, key: K) -> Option<V>
|
||||
where
|
||||
K: Into<BorrowedKey<'a>>,
|
||||
K: Into<RefKey<'a>>,
|
||||
{
|
||||
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<V> Dict<V> {
|
||||
}
|
||||
|
||||
/// Iterator over all borrowed keys and values.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (BorrowedKey, &V)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (RefKey, &V)> {
|
||||
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<V> Dict<V> {
|
||||
}
|
||||
|
||||
/// Move into an owned iterator over owned keys and values.
|
||||
pub fn into_iter(self) -> impl Iterator<Item = (OwnedKey, V)> {
|
||||
pub fn into_iter(self) -> impl Iterator<Item = (DictKey, V)> {
|
||||
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<V> Dict<V> {
|
||||
|
||||
impl<'a, K, V> Index<K> for Dict<V>
|
||||
where
|
||||
K: Into<BorrowedKey<'a>>,
|
||||
K: Into<RefKey<'a>>,
|
||||
{
|
||||
type Output = V;
|
||||
|
||||
@ -230,33 +230,39 @@ impl<V: Debug> Debug for Dict<V> {
|
||||
|
||||
/// 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<BorrowedKey<'_>> for OwnedKey {
|
||||
fn from(key: BorrowedKey<'_>) -> Self {
|
||||
impl From<&Self> for DictKey {
|
||||
fn from(key: &Self) -> Self {
|
||||
key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RefKey<'_>> 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<u64> for OwnedKey {
|
||||
impl From<u64> for DictKey {
|
||||
fn from(num: u64) -> Self {
|
||||
Self::Num(num)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for OwnedKey {
|
||||
impl From<String> 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<u64> for BorrowedKey<'static> {
|
||||
impl From<u64> 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)
|
||||
}
|
||||
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
// 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",
|
||||
|
@ -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),
|
||||
|
@ -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<MultiLayout> {
|
||||
pub async fn layout_tree(tree: &SynTree, ctx: LayoutContext<'_>) -> Pass<MultiLayout> {
|
||||
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<SyntaxNode>) {
|
||||
async fn layout_node(&mut self, node: &Spanned<SynNode>) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<SyntaxTree> {
|
||||
pub fn parse(&self, src: &str) -> Pass<SynTree> {
|
||||
parse::parse(src)
|
||||
}
|
||||
|
||||
/// Layout a syntax tree and return the produced layout.
|
||||
pub async fn layout(&self, tree: &SyntaxTree) -> Pass<MultiLayout> {
|
||||
pub async fn layout(&self, tree: &SynTree) -> Pass<MultiLayout> {
|
||||
use crate::layout::prelude::*;
|
||||
|
||||
let margins = self.style.page.margins();
|
||||
|
@ -17,7 +17,7 @@ use super::*;
|
||||
pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass<Value> {
|
||||
let mut f = Feedback::new();
|
||||
|
||||
let content = args.take::<SyntaxTree>();
|
||||
let content = args.take::<SynTree>();
|
||||
let h = args.take_key::<Spanned<SpecAlign>>("horizontal", &mut f);
|
||||
let v = args.take_key::<Spanned<SpecAlign>>("vertical", &mut f);
|
||||
let all = args
|
||||
|
@ -13,7 +13,7 @@ pub async fn boxed(
|
||||
) -> Pass<Value> {
|
||||
let mut f = Feedback::new();
|
||||
|
||||
let content = args.take::<SyntaxTree>().unwrap_or(SyntaxTree::new());
|
||||
let content = args.take::<SynTree>().unwrap_or(SynTree::new());
|
||||
|
||||
ctx.base = ctx.spaces[0].size;
|
||||
ctx.spaces.truncate(1);
|
||||
|
@ -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::<SyntaxTree>();
|
||||
let content = args.take::<SynTree>();
|
||||
|
||||
if let Some(s) = args.take::<ScaleLength>() {
|
||||
match s {
|
||||
|
148
src/parse/mod.rs
148
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<SyntaxTree> {
|
||||
pub fn parse(src: &str) -> Pass<SynTree> {
|
||||
Parser::new(src).parse()
|
||||
}
|
||||
|
||||
@ -42,7 +41,7 @@ impl<'s> Parser<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(mut self) -> Pass<SyntaxTree> {
|
||||
fn parse(mut self) -> Pass<SynTree> {
|
||||
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<Spanned<SyntaxNode>> {
|
||||
fn parse_node(&mut self) -> Option<Spanned<SynNode>> {
|
||||
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<Heading> {
|
||||
fn parse_heading(&mut self) -> Spanned<NodeHeading> {
|
||||
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<CallExpr> {
|
||||
fn parse_bracket_call(&mut self, chained: bool) -> Spanned<ExprCall> {
|
||||
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<Ident>) -> Spanned<CallExpr> {
|
||||
fn parse_paren_call(&mut self, name: Spanned<Ident>) -> Spanned<ExprCall> {
|
||||
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<Spanned<Expr>>, Box<Spanned<Expr>>) -> Expr;
|
||||
|
||||
// Expressions and values.
|
||||
impl Parser<'_> {
|
||||
fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
|
||||
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<Spanned<Expr>> {
|
||||
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<Spanned<Expr>>,
|
||||
mut parse_op: impl FnMut(Token) -> Option<Binop>,
|
||||
mut parse_op: impl FnMut(Token) -> Option<BinOp>,
|
||||
) -> Option<Spanned<Expr>> {
|
||||
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,
|
||||
|
@ -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<char> {
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
@ -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<Ident> {
|
||||
}
|
||||
|
||||
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::<Spanned<&str>>::into($key);
|
||||
let val = Into::<Spanned<Expr>>::into($value);
|
||||
$dict.insert(key.v, SpannedEntry::new(key.span, val));
|
||||
let key = key.map(Into::<DictKey>::into);
|
||||
let value = Into::<Spanned<Expr>>::into($value);
|
||||
$dict.0.push(LitDictEntry { key: Some(key), value });
|
||||
Dict![@dict=$dict, $($($tts)*)?];
|
||||
}};
|
||||
(@dict=$dict:expr, $value:expr $(, $($tts:tt)*)?) => {
|
||||
let val = Into::<Spanned<Expr>>::into($value);
|
||||
$dict.push(SpannedEntry::val(val));
|
||||
let value = Into::<Spanned<Expr>>::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::<Spanned<SyntaxNode>>::into($node)),*]
|
||||
vec![$(Into::<Spanned<SynNode>>::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::<Spanned<&str>>::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<T: Into<Spanned<Expr>>>(e1: T) -> Expr {
|
||||
Expr::Neg(Box::new(e1.into()))
|
||||
fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr {
|
||||
Expr::Unary(ExprUnary {
|
||||
op: op.into(),
|
||||
expr: expr.into().map(Box::new),
|
||||
})
|
||||
}
|
||||
fn Add<T: Into<Spanned<Expr>>>(e1: T, e2: T) -> Expr {
|
||||
Expr::Add(Box::new(e1.into()), Box::new(e2.into()))
|
||||
}
|
||||
fn Sub<T: Into<Spanned<Expr>>>(e1: T, e2: T) -> Expr {
|
||||
Expr::Sub(Box::new(e1.into()), Box::new(e2.into()))
|
||||
}
|
||||
fn Mul<T: Into<Spanned<Expr>>>(e1: T, e2: T) -> Expr {
|
||||
Expr::Mul(Box::new(e1.into()), Box::new(e2.into()))
|
||||
}
|
||||
fn Div<T: Into<Spanned<Expr>>>(e1: T, e2: T) -> Expr {
|
||||
Expr::Div(Box::new(e1.into()), Box::new(e2.into()))
|
||||
fn Binary(
|
||||
op: impl Into<Spanned<BinOp>>,
|
||||
lhs: impl Into<Spanned<Expr>>,
|
||||
rhs: impl Into<Spanned<Expr>>,
|
||||
) -> 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"));
|
||||
|
124
src/syntax/expr.rs
Normal file
124
src/syntax/expr.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
58
src/syntax/ident.rs
Normal file
58
src/syntax/ident.rs
Normal file
@ -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 == '_'
|
||||
}
|
99
src/syntax/lit.rs
Normal file
99
src/syntax/lit.rs
Normal file
@ -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>,
|
||||
}
|
@ -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)]
|
||||
|
@ -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> {
|
||||
|
@ -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,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user