Reorganize ast types 🏕

This commit is contained in:
Laurenz 2020-10-01 15:03:37 +02:00
parent aafd3c95ca
commit 7fcad452b8
17 changed files with 554 additions and 447 deletions

View File

@ -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)
}

View File

@ -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",

View File

@ -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),

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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

View File

@ -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);

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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
View 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
View 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
View 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>,
}

View File

@ -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)]

View File

@ -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> {

View File

@ -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,
}