mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Merge pull request #2 from typst/expressions
Add parsing capabilities for mathematical expressions
This commit is contained in:
commit
00266f6a58
@ -132,7 +132,7 @@ macro_rules! function {
|
|||||||
let func = $code;
|
let func = $code;
|
||||||
|
|
||||||
for arg in header.args.into_iter() {
|
for arg in header.args.into_iter() {
|
||||||
feedback.errors.push(err!(arg.span(); "unexpected argument"));
|
feedback.errors.push(err!(arg.span; "unexpected argument"));
|
||||||
}
|
}
|
||||||
|
|
||||||
$crate::Pass::new(func, feedback)
|
$crate::Pass::new(func, feedback)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Expressions in function headers.
|
//! Expressions in function headers.
|
||||||
|
|
||||||
use std::fmt::{self, Write, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -26,7 +26,7 @@ pub enum Expr {
|
|||||||
Size(Size),
|
Size(Size),
|
||||||
/// A bool: `true, false`.
|
/// A bool: `true, false`.
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
/// A color value, including the alpha channel: `#f79143ff`
|
/// A color value, including the alpha channel: `#f79143ff`.
|
||||||
Color(RgbaColor),
|
Color(RgbaColor),
|
||||||
/// A tuple: `(false, 12cm, "hi")`.
|
/// A tuple: `(false, 12cm, "hi")`.
|
||||||
Tuple(Tuple),
|
Tuple(Tuple),
|
||||||
@ -34,6 +34,16 @@ pub enum Expr {
|
|||||||
NamedTuple(NamedTuple),
|
NamedTuple(NamedTuple),
|
||||||
/// An object: `{ fit: false, size: 12pt }`.
|
/// An object: `{ fit: false, size: 12pt }`.
|
||||||
Object(Object),
|
Object(Object),
|
||||||
|
/// An operator that negates the contained expression.
|
||||||
|
Neg(Box<Spanned<Expr>>),
|
||||||
|
/// An operator that adds the contained expressions.
|
||||||
|
Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
||||||
|
/// An operator that subtracts contained expressions.
|
||||||
|
Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
||||||
|
/// An operator that multiplies the contained expressions.
|
||||||
|
Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
||||||
|
/// An operator that divides the contained expressions.
|
||||||
|
Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
@ -50,6 +60,11 @@ impl Expr {
|
|||||||
Tuple(_) => "tuple",
|
Tuple(_) => "tuple",
|
||||||
NamedTuple(_) => "named tuple",
|
NamedTuple(_) => "named tuple",
|
||||||
Object(_) => "object",
|
Object(_) => "object",
|
||||||
|
Neg(_) => "negation",
|
||||||
|
Add(_, _) => "addition",
|
||||||
|
Sub(_, _) => "subtraction",
|
||||||
|
Mul(_, _) => "multiplication",
|
||||||
|
Div(_, _) => "division",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,6 +82,11 @@ impl Debug for Expr {
|
|||||||
Tuple(t) => t.fmt(f),
|
Tuple(t) => t.fmt(f),
|
||||||
NamedTuple(t) => t.fmt(f),
|
NamedTuple(t) => t.fmt(f),
|
||||||
Object(o) => o.fmt(f),
|
Object(o) => o.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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,9 +122,7 @@ impl Ident {
|
|||||||
|
|
||||||
impl Debug for Ident {
|
impl Debug for Ident {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_char('`')?;
|
write!(f, "`{}`", self.0)
|
||||||
f.write_str(&self.0)?;
|
|
||||||
f.write_char('`')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +364,7 @@ impl Deref for NamedTuple {
|
|||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
/// The key-value pairs of the object.
|
/// The key-value pairs of the object.
|
||||||
pub pairs: Vec<Pair>,
|
pub pairs: Vec<Spanned<Pair>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A key-value pair in an object.
|
/// A key-value pair in an object.
|
||||||
@ -373,7 +391,7 @@ impl Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a pair to object.
|
/// Add a pair to object.
|
||||||
pub fn add(&mut self, pair: Pair) {
|
pub fn add(&mut self, pair: Spanned<Pair>) {
|
||||||
self.pairs.push(pair);
|
self.pairs.push(pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,7 +401,7 @@ impl Object {
|
|||||||
/// Inserts an error if the value does not match. If the key is not
|
/// Inserts an error if the value does not match. If the key is not
|
||||||
/// contained, no error is inserted.
|
/// contained, no error is inserted.
|
||||||
pub fn get<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V> {
|
pub fn get<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V> {
|
||||||
let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?;
|
let index = self.pairs.iter().position(|pair| pair.v.key.v.as_str() == key)?;
|
||||||
self.get_index::<V>(errors, index)
|
self.get_index::<V>(errors, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +414,7 @@ impl Object {
|
|||||||
errors: &mut Errors,
|
errors: &mut Errors,
|
||||||
) -> Option<(K, V)> {
|
) -> Option<(K, V)> {
|
||||||
for (index, pair) in self.pairs.iter().enumerate() {
|
for (index, pair) in self.pairs.iter().enumerate() {
|
||||||
let key = Spanned { v: pair.key.v.as_str(), span: pair.key.span };
|
let key = Spanned { v: pair.v.key.v.as_str(), span: pair.v.key.span };
|
||||||
if let Some(key) = K::parse(key) {
|
if let Some(key) = K::parse(key) {
|
||||||
return self.get_index::<V>(errors, index).map(|value| (key, value));
|
return self.get_index::<V>(errors, index).map(|value| (key, value));
|
||||||
}
|
}
|
||||||
@ -414,7 +432,7 @@ impl Object {
|
|||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
std::iter::from_fn(move || {
|
std::iter::from_fn(move || {
|
||||||
if index < self.pairs.len() {
|
if index < self.pairs.len() {
|
||||||
let key = &self.pairs[index].key;
|
let key = &self.pairs[index].v.key;
|
||||||
let key = Spanned { v: key.v.as_str(), span: key.span };
|
let key = Spanned { v: key.v.as_str(), span: key.span };
|
||||||
|
|
||||||
Some(if let Some(key) = K::parse(key) {
|
Some(if let Some(key) = K::parse(key) {
|
||||||
@ -447,7 +465,7 @@ impl Object {
|
|||||||
/// Extract the argument at the given index and insert an error if the value
|
/// Extract the argument at the given index and insert an error if the value
|
||||||
/// does not match.
|
/// does not match.
|
||||||
fn get_index<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V> {
|
fn get_index<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V> {
|
||||||
let expr = self.pairs.remove(index).value;
|
let expr = self.pairs.remove(index).v.value;
|
||||||
let span = expr.span;
|
let span = expr.span;
|
||||||
match V::parse(expr) {
|
match V::parse(expr) {
|
||||||
Ok(output) => Some(output),
|
Ok(output) => Some(output),
|
||||||
@ -456,14 +474,14 @@ impl Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the pairs of this object.
|
/// Iterate over the pairs of this object.
|
||||||
pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Pair> {
|
pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned<Pair>> {
|
||||||
self.pairs.iter()
|
self.pairs.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Object {
|
impl IntoIterator for Object {
|
||||||
type Item = Pair;
|
type Item = Spanned<Pair>;
|
||||||
type IntoIter = std::vec::IntoIter<Pair>;
|
type IntoIter = std::vec::IntoIter<Spanned<Pair>>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.pairs.into_iter()
|
self.pairs.into_iter()
|
||||||
@ -471,16 +489,16 @@ impl IntoIterator for Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Object {
|
impl<'a> IntoIterator for &'a Object {
|
||||||
type Item = &'a Pair;
|
type Item = &'a Spanned<Pair>;
|
||||||
type IntoIter = std::slice::Iter<'a, Pair>;
|
type IntoIter = std::slice::Iter<'a, Spanned<Pair>>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.iter()
|
self.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<Pair> for Object {
|
impl FromIterator<Spanned<Pair>> for Object {
|
||||||
fn from_iter<I: IntoIterator<Item=Pair>>(iter: I) -> Self {
|
fn from_iter<I: IntoIterator<Item=Spanned<Pair>>>(iter: I) -> Self {
|
||||||
Object { pairs: iter.into_iter().collect() }
|
Object { pairs: iter.into_iter().collect() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,7 +506,7 @@ impl FromIterator<Pair> for Object {
|
|||||||
impl Debug for Object {
|
impl Debug for Object {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.debug_map()
|
f.debug_map()
|
||||||
.entries(self.pairs.iter().map(|p| (&p.key.v, &p.value.v)))
|
.entries(self.pairs.iter().map(|p| (&p.v.key.v, &p.v.value.v)))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,22 +42,27 @@ impl FuncArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add an argument.
|
/// Add an argument.
|
||||||
pub fn add(&mut self, arg: FuncArg) {
|
pub fn add(&mut self, arg: Spanned<FuncArg>) {
|
||||||
match arg {
|
match arg.v {
|
||||||
FuncArg::Pos(item) => self.pos.add(item),
|
FuncArg::Pos(item) => self.pos.add(Spanned::new(item, arg.span)),
|
||||||
FuncArg::Key(pair) => self.key.add(pair),
|
FuncArg::Key(pair) => self.key.add(Spanned::new(pair, arg.span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all arguments.
|
/// Iterate over all arguments.
|
||||||
pub fn into_iter(self) -> impl Iterator<Item=FuncArg> {
|
pub fn into_iter(self) -> impl Iterator<Item=Spanned<FuncArg>> {
|
||||||
self.pos.items.into_iter().map(|item| FuncArg::Pos(item))
|
let pos = self.pos.items.into_iter()
|
||||||
.chain(self.key.pairs.into_iter().map(|pair| FuncArg::Key(pair)))
|
.map(|spanned| spanned.map(|item| FuncArg::Pos(item)));
|
||||||
|
|
||||||
|
let key = self.key.pairs.into_iter()
|
||||||
|
.map(|spanned| spanned.map(|pair| FuncArg::Key(pair)));
|
||||||
|
|
||||||
|
pos.chain(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<FuncArg> for FuncArgs {
|
impl FromIterator<Spanned<FuncArg>> for FuncArgs {
|
||||||
fn from_iter<I: IntoIterator<Item=FuncArg>>(iter: I) -> Self {
|
fn from_iter<I: IntoIterator<Item=Spanned<FuncArg>>>(iter: I) -> Self {
|
||||||
let mut args = FuncArgs::new();
|
let mut args = FuncArgs::new();
|
||||||
for item in iter.into_iter() {
|
for item in iter.into_iter() {
|
||||||
args.add(item);
|
args.add(item);
|
||||||
@ -70,24 +75,11 @@ impl FromIterator<FuncArg> for FuncArgs {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum FuncArg {
|
pub enum FuncArg {
|
||||||
/// A positional argument.
|
/// A positional argument.
|
||||||
Pos(Spanned<Expr>),
|
Pos(Expr),
|
||||||
/// A keyword argument.
|
/// A keyword argument.
|
||||||
Key(Pair),
|
Key(Pair),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncArg {
|
|
||||||
/// The full span of this argument.
|
|
||||||
///
|
|
||||||
/// In case of a positional argument this is just the span of the expression
|
|
||||||
/// and in case of a keyword argument the combined span of key and value.
|
|
||||||
pub fn span(&self) -> Span {
|
|
||||||
match self {
|
|
||||||
FuncArg::Pos(item) => item.span,
|
|
||||||
FuncArg::Key(Pair { key, value }) => Span::merge(key.span, value.span),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extra methods on [`Options`](Option) used for argument parsing.
|
/// Extra methods on [`Options`](Option) used for argument parsing.
|
||||||
pub trait OptionExt: Sized {
|
pub trait OptionExt: Sized {
|
||||||
/// Add an error about a missing argument `arg` with the given span if the
|
/// Add an error about a missing argument `arg` with the given span if the
|
||||||
|
@ -190,10 +190,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
if let Some(ident) = p.parse_ident() {
|
if let Some(ident) = p.parse_ident() {
|
||||||
// This could still be a named tuple
|
// This could still be a named tuple
|
||||||
if let Some(Token::LeftParen) = p.peekv() {
|
if let Some(Token::LeftParen) = p.peekv() {
|
||||||
return Ok(FuncArg::Pos(
|
let tuple = p.parse_named_tuple(ident);
|
||||||
p.parse_named_tuple(ident)
|
return Ok(tuple.map(|t| FuncArg::Pos(Expr::NamedTuple(t))));
|
||||||
.map(|t| Expr::NamedTuple(t))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.skip_whitespace();
|
p.skip_whitespace();
|
||||||
@ -209,23 +207,100 @@ impl<'s> FuncParser<'s> {
|
|||||||
let value = p.parse_expr().ok_or(("value", None))?;
|
let value = p.parse_expr().ok_or(("value", None))?;
|
||||||
|
|
||||||
// Add a keyword argument.
|
// Add a keyword argument.
|
||||||
Ok(FuncArg::Key(Pair { key: ident, value }))
|
let span = Span::merge(ident.span, value.span);
|
||||||
|
let pair = Pair { key: ident, value };
|
||||||
|
Ok(Spanned::new(FuncArg::Key(pair), span))
|
||||||
} else {
|
} else {
|
||||||
// Add a positional argument because there was no equals
|
// Add a positional argument because there was no equals
|
||||||
// sign after the identifier that could have been a key.
|
// sign after the identifier that could have been a key.
|
||||||
Ok(FuncArg::Pos(ident.map(|id| Expr::Ident(id))))
|
Ok(ident.map(|id| FuncArg::Pos(Expr::Ident(id))))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add a positional argument because we haven't got an
|
// Add a positional argument because we haven't got an
|
||||||
// identifier that could be an argument key.
|
// identifier that could be an argument key.
|
||||||
p.parse_expr().map(|expr| FuncArg::Pos(expr))
|
let value = p.parse_expr().ok_or(("argument", None))?;
|
||||||
.ok_or(("argument", None))
|
Ok(value.map(|expr| FuncArg::Pos(expr)))
|
||||||
}
|
}
|
||||||
}).v
|
}).v
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an atomic or compound (tuple / object) expression.
|
/// Parse an expression which may contain math operands. For this, this
|
||||||
|
/// method looks for operators in descending order of associativity, i.e. we
|
||||||
|
/// first drill down to find all negations, brackets and tuples, the next
|
||||||
|
/// level, we look for multiplication and division and here finally, for
|
||||||
|
/// addition and subtraction.
|
||||||
fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
|
fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
|
||||||
|
let o1 = self.parse_term()?;
|
||||||
|
self.parse_binop(o1, "summand", Self::parse_expr, |token| match token {
|
||||||
|
Token::Plus => Some(Expr::Add),
|
||||||
|
Token::Hyphen => Some(Expr::Sub),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_term(&mut self) -> Option<Spanned<Expr>> {
|
||||||
|
let o1 = self.parse_factor()?;
|
||||||
|
self.parse_binop(o1, "factor", Self::parse_term, |token| match token {
|
||||||
|
Token::Star => Some(Expr::Mul),
|
||||||
|
Token::Slash => Some(Expr::Div),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_binop<F, G>(
|
||||||
|
&mut self,
|
||||||
|
o1: Spanned<Expr>,
|
||||||
|
operand_name: &str,
|
||||||
|
parse_operand: F,
|
||||||
|
parse_op: G,
|
||||||
|
) -> Option<Spanned<Expr>>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> Option<Spanned<Expr>>,
|
||||||
|
G: FnOnce(Token) -> Option<fn(Box<Spanned<Expr>>, Box<Spanned<Expr>>) -> Expr>,
|
||||||
|
{
|
||||||
|
self.skip_whitespace();
|
||||||
|
|
||||||
|
if let Some(next) = self.peek() {
|
||||||
|
if let Some(binop) = parse_op(next.v) {
|
||||||
|
self.eat();
|
||||||
|
self.skip_whitespace();
|
||||||
|
|
||||||
|
if let Some(o2) = parse_operand(self) {
|
||||||
|
let span = Span::merge(o1.span, o2.span);
|
||||||
|
let expr = binop(Box::new(o1), Box::new(o2));
|
||||||
|
return Some(Spanned::new(expr, span));
|
||||||
|
} else {
|
||||||
|
self.feedback.errors.push(err!(
|
||||||
|
Span::merge(next.span, o1.span);
|
||||||
|
"missing right {}", operand_name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(o1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse expressions that are of the form value or -value.
|
||||||
|
fn parse_factor(&mut self) -> Option<Spanned<Expr>> {
|
||||||
|
let first = self.peek()?;
|
||||||
|
if first.v == Token::Hyphen {
|
||||||
|
self.eat();
|
||||||
|
self.skip_whitespace();
|
||||||
|
|
||||||
|
if let Some(factor) = self.parse_value() {
|
||||||
|
let span = Span::merge(first.span, factor.span);
|
||||||
|
Some(Spanned::new(Expr::Neg(Box::new(factor)), span))
|
||||||
|
} else {
|
||||||
|
self.feedback.errors.push(err!(first.span; "dangling minus"));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.parse_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_value(&mut self) -> Option<Spanned<Expr>> {
|
||||||
let first = self.peek()?;
|
let first = self.peek()?;
|
||||||
macro_rules! take {
|
macro_rules! take {
|
||||||
($v:expr) => ({ self.eat(); Spanned { v: $v, span: first.span } });
|
($v:expr) => ({ self.eat(); Spanned { v: $v, span: first.span } });
|
||||||
@ -263,27 +338,36 @@ impl<'s> FuncParser<'s> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Token::LeftParen => self.parse_tuple().map(|t| Expr::Tuple(t)),
|
Token::LeftParen => {
|
||||||
|
let (mut tuple, can_be_coerced) = self.parse_tuple();
|
||||||
|
// Coerce 1-tuple into value
|
||||||
|
if can_be_coerced && tuple.v.items.len() > 0 {
|
||||||
|
tuple.v.items.pop().expect("length is at least one")
|
||||||
|
} else {
|
||||||
|
tuple.map(|t| Expr::Tuple(t))
|
||||||
|
}
|
||||||
|
},
|
||||||
Token::LeftBrace => self.parse_object().map(|o| Expr::Object(o)),
|
Token::LeftBrace => self.parse_object().map(|o| Expr::Object(o)),
|
||||||
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a tuple expression: `(<expr>, ...)`.
|
/// Parse a tuple expression: `(<expr>, ...)`. The boolean in the return
|
||||||
fn parse_tuple(&mut self) -> Spanned<Tuple> {
|
/// values showes whether the tuple can be coerced into a single value.
|
||||||
|
fn parse_tuple(&mut self) -> (Spanned<Tuple>, bool) {
|
||||||
let token = self.eat();
|
let token = self.eat();
|
||||||
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftParen));
|
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftParen));
|
||||||
|
|
||||||
// Parse a collection until a right paren appears and complain about
|
// Parse a collection until a right paren appears and complain about
|
||||||
// missing a `value` when an invalid token is encoutered.
|
// missing a `value` when an invalid token is encoutered.
|
||||||
self.parse_collection(Some(Token::RightParen),
|
self.parse_collection_comma_aware(Some(Token::RightParen),
|
||||||
|p| p.parse_expr().ok_or(("value", None)))
|
|p| p.parse_expr().ok_or(("value", None)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a tuple expression: `name(<expr>, ...)` with a given identifier.
|
/// Parse a tuple expression: `name(<expr>, ...)` with a given identifier.
|
||||||
fn parse_named_tuple(&mut self, name: Spanned<Ident>) -> Spanned<NamedTuple> {
|
fn parse_named_tuple(&mut self, name: Spanned<Ident>) -> Spanned<NamedTuple> {
|
||||||
let tuple = self.parse_tuple();
|
let tuple = self.parse_tuple().0;
|
||||||
let span = Span::merge(name.span, tuple.span);
|
let span = Span::merge(name.span, tuple.span);
|
||||||
Spanned::new(NamedTuple::new(name, tuple), span)
|
Spanned::new(NamedTuple::new(name, tuple), span)
|
||||||
}
|
}
|
||||||
@ -314,7 +398,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
|
|
||||||
let value = p.parse_expr().ok_or(("value", None))?;
|
let value = p.parse_expr().ok_or(("value", None))?;
|
||||||
|
|
||||||
Ok(Pair { key, value })
|
let span = Span::merge(key.span, value.span);
|
||||||
|
Ok(Spanned::new(Pair { key, value }, span))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,13 +408,30 @@ impl<'s> FuncParser<'s> {
|
|||||||
fn parse_collection<C, I, F>(
|
fn parse_collection<C, I, F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
end: Option<Token>,
|
end: Option<Token>,
|
||||||
mut parse_item: F
|
parse_item: F
|
||||||
) -> Spanned<C>
|
) -> Spanned<C>
|
||||||
where
|
where
|
||||||
C: FromIterator<I>,
|
C: FromIterator<Spanned<I>>,
|
||||||
F: FnMut(&mut Self) -> Result<I, (&'static str, Option<Position>)>,
|
F: FnMut(&mut Self) -> Result<Spanned<I>, (&'static str, Option<Position>)>,
|
||||||
|
{
|
||||||
|
self.parse_collection_comma_aware(end, parse_item).0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a comma-separated collection where each item is parsed through
|
||||||
|
/// `parse_item` until the `end` token is met. The first item in the return
|
||||||
|
/// tuple is the collection, the second item indicates whether the
|
||||||
|
/// collection can be coerced into a single item (i.e. no comma appeared).
|
||||||
|
fn parse_collection_comma_aware<C, I, F>(
|
||||||
|
&mut self,
|
||||||
|
end: Option<Token>,
|
||||||
|
mut parse_item: F
|
||||||
|
) -> (Spanned<C>, bool)
|
||||||
|
where
|
||||||
|
C: FromIterator<Spanned<I>>,
|
||||||
|
F: FnMut(&mut Self) -> Result<Spanned<I>, (&'static str, Option<Position>)>,
|
||||||
{
|
{
|
||||||
let start = self.pos();
|
let start = self.pos();
|
||||||
|
let mut can_be_coerced = true;
|
||||||
|
|
||||||
// Parse the comma separated items.
|
// Parse the comma separated items.
|
||||||
let collection = std::iter::from_fn(|| {
|
let collection = std::iter::from_fn(|| {
|
||||||
@ -356,11 +458,16 @@ impl<'s> FuncParser<'s> {
|
|||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
// Expect a comma behind the item (only separated by
|
// Expect a comma behind the item (only separated by
|
||||||
// whitespace).
|
// whitespace).
|
||||||
let behind_item = self.pos();
|
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
match self.peekv() {
|
match self.peekv() {
|
||||||
Some(Token::Comma) => { self.eat(); }
|
Some(Token::Comma) => {
|
||||||
t @ Some(_) if t != end => self.expected_at("comma", behind_item),
|
can_be_coerced = false;
|
||||||
|
self.eat();
|
||||||
|
}
|
||||||
|
t @ Some(_) if t != end => {
|
||||||
|
can_be_coerced = false;
|
||||||
|
self.expected_at("comma", item.span.end);
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,7 +490,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
}).filter_map(|x| x).collect();
|
}).filter_map(|x| x).collect();
|
||||||
|
|
||||||
let end = self.pos();
|
let end = self.pos();
|
||||||
Spanned::new(collection, Span { start, end })
|
(Spanned::new(collection, Span { start, end }), can_be_coerced)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to parse an identifier and do nothing if the peekable token is no
|
/// Try to parse an identifier and do nothing if the peekable token is no
|
||||||
@ -546,6 +653,19 @@ mod tests {
|
|||||||
fn Id(text: &str) -> Expr { Expr::Ident(Ident(text.to_string())) }
|
fn Id(text: &str) -> Expr { Expr::Ident(Ident(text.to_string())) }
|
||||||
fn Str(text: &str) -> Expr { Expr::Str(text.to_string()) }
|
fn Str(text: &str) -> Expr { Expr::Str(text.to_string()) }
|
||||||
fn Pt(points: f32) -> Expr { Expr::Size(Size::pt(points)) }
|
fn Pt(points: f32) -> Expr { Expr::Size(Size::pt(points)) }
|
||||||
|
fn Neg(e1: Expr) -> Expr { Expr::Neg(Box::new(zspan(e1))) }
|
||||||
|
fn Add(e1: Expr, e2: Expr) -> Expr {
|
||||||
|
Expr::Add(Box::new(zspan(e1)), Box::new(zspan(e2)))
|
||||||
|
}
|
||||||
|
fn Sub(e1: Expr, e2: Expr) -> Expr {
|
||||||
|
Expr::Sub(Box::new(zspan(e1)), Box::new(zspan(e2)))
|
||||||
|
}
|
||||||
|
fn Mul(e1: Expr, e2: Expr) -> Expr {
|
||||||
|
Expr::Mul(Box::new(zspan(e1)), Box::new(zspan(e2)))
|
||||||
|
}
|
||||||
|
fn Div(e1: Expr, e2: Expr) -> Expr {
|
||||||
|
Expr::Div(Box::new(zspan(e1)), Box::new(zspan(e2)))
|
||||||
|
}
|
||||||
|
|
||||||
fn Clr(r: u8, g: u8, b: u8, a: u8) -> Expr {
|
fn Clr(r: u8, g: u8, b: u8, a: u8) -> Expr {
|
||||||
Expr::Color(RgbaColor::new(r, g, b, a))
|
Expr::Color(RgbaColor::new(r, g, b, a))
|
||||||
@ -589,10 +709,10 @@ mod tests {
|
|||||||
macro_rules! object {
|
macro_rules! object {
|
||||||
($($key:expr => $value:expr),* $(,)?) => {
|
($($key:expr => $value:expr),* $(,)?) => {
|
||||||
Expr::Object(Object {
|
Expr::Object(Object {
|
||||||
pairs: vec![$(Pair {
|
pairs: vec![$(zspan(Pair {
|
||||||
key: zspan(Ident($key.to_string())),
|
key: zspan(Ident($key.to_string())),
|
||||||
value: zspan($value),
|
value: zspan($value),
|
||||||
}),*]
|
})),*]
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -814,6 +934,12 @@ mod tests {
|
|||||||
p!("[val: 12e1pt]" => [func!("val": (Pt(12e1)), {})]);
|
p!("[val: 12e1pt]" => [func!("val": (Pt(12e1)), {})]);
|
||||||
p!("[val: #f7a20500]" => [func!("val": (ClrStr("f7a20500")), {})]);
|
p!("[val: #f7a20500]" => [func!("val": (ClrStr("f7a20500")), {})]);
|
||||||
|
|
||||||
|
// Math
|
||||||
|
p!("[val: 3.2in + 6pt]" => [func!("val": (Add(Sz(Size::inches(3.2)), Sz(Size::pt(6.0)))), {})]);
|
||||||
|
p!("[val: 5 - 0.01]" => [func!("val": (Sub(Num(5.0), Num(0.01))), {})]);
|
||||||
|
p!("[val: (3mm * 2)]" => [func!("val": (Mul(Sz(Size::mm(3.0)), Num(2.0))), {})]);
|
||||||
|
p!("[val: 12e-3cm/1pt]" => [func!("val": (Div(Sz(Size::cm(12e-3)), Sz(Size::pt(1.0)))), {})]);
|
||||||
|
|
||||||
// Unclosed string.
|
// Unclosed string.
|
||||||
p!("[val: \"hello]" => [func!("val": (Str("hello]")), {})], [
|
p!("[val: \"hello]" => [func!("val": (Str("hello]")), {})], [
|
||||||
(0:13, 0:13, "expected quote"),
|
(0:13, 0:13, "expected quote"),
|
||||||
@ -835,6 +961,30 @@ mod tests {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_complex_mathematical_expressions() {
|
||||||
|
p!("[val: (3.2in + 6pt)*(5/2-1)]" => [func!("val": (
|
||||||
|
Mul(
|
||||||
|
Add(Sz(Size::inches(3.2)), Sz(Size::pt(6.0))),
|
||||||
|
Sub(Div(Num(5.0), Num(2.0)), Num(1.0))
|
||||||
|
)
|
||||||
|
), {})]);
|
||||||
|
p!("[val: (6.3E+2+4* - 3.2pt)/2]" => [func!("val": (
|
||||||
|
Div(Add(Num(6.3e2),Mul(Num(4.0), Neg(Pt(3.2)))), Num(2.0))
|
||||||
|
), {})]);
|
||||||
|
p!("[val: 4pt--]" =>
|
||||||
|
[func!("val": (Pt(4.0)), {})],
|
||||||
|
[
|
||||||
|
(0:10, 0:11, "dangling minus"),
|
||||||
|
(0:6, 0:10, "missing right summand")
|
||||||
|
],
|
||||||
|
);
|
||||||
|
p!("[val: 3mm+4pt*]" =>
|
||||||
|
[func!("val": (Add(Sz(Size::mm(3.0)), Pt(4.0))), {})],
|
||||||
|
[(0:10, 0:14, "missing right factor")],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_tuples() {
|
fn parse_tuples() {
|
||||||
// Empty tuple
|
// Empty tuple
|
||||||
@ -858,9 +1008,9 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Unclosed tuple
|
// Unclosed tuple
|
||||||
p!("[val: (hello]" =>
|
p!("[val: (hello,]" =>
|
||||||
[func!("val": (tuple!(Id("hello"))), {})],
|
[func!("val": (tuple!(Id("hello"),)), {})],
|
||||||
[(0:12, 0:12, "expected closing paren")],
|
[(0:13, 0:13, "expected closing paren")],
|
||||||
);
|
);
|
||||||
p!("[val: lang(中文]" =>
|
p!("[val: lang(中文]" =>
|
||||||
[func!("val": (named_tuple!("lang", Id("中文"))), {})],
|
[func!("val": (named_tuple!("lang", Id("中文"))), {})],
|
||||||
@ -882,8 +1032,8 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Nested tuples
|
// Nested tuples
|
||||||
p!("[val: (1, (2))]" =>
|
p!("[val: (1, (2, 3))]" =>
|
||||||
[func!("val": (tuple!(Num(1.0), tuple!(Num(2.0)))), {})]
|
[func!("val": (tuple!(Num(1.0), tuple!(Num(2.0), Num(3.0)))), {})]
|
||||||
);
|
);
|
||||||
p!("[val: css(1pt, rgb(90, 102, 254), \"solid\")]" =>
|
p!("[val: css(1pt, rgb(90, 102, 254), \"solid\")]" =>
|
||||||
[func!("val": (named_tuple!(
|
[func!("val": (named_tuple!(
|
||||||
@ -1085,7 +1235,7 @@ mod tests {
|
|||||||
// Body nodes in bodies.
|
// Body nodes in bodies.
|
||||||
p!("[val:*][*Hi*]" =>
|
p!("[val:*][*Hi*]" =>
|
||||||
[func!("val"; [Bold, T("Hi"), Bold])],
|
[func!("val"; [Bold, T("Hi"), Bold])],
|
||||||
[(0:5, 0:6, "expected argument, found invalid token")],
|
[(0:5, 0:6, "expected argument, found star")],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Errors in bodies.
|
// Errors in bodies.
|
||||||
|
@ -88,13 +88,6 @@ pub trait SpanlessEq<Rhs=Self> {
|
|||||||
fn spanless_eq(&self, other: &Rhs) -> bool;
|
fn spanless_eq(&self, other: &Rhs) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SpanlessEq> SpanlessEq for Vec<Spanned<T>> {
|
|
||||||
fn spanless_eq(&self, other: &Vec<Spanned<T>>) -> bool {
|
|
||||||
self.len() == other.len()
|
|
||||||
&& self.iter().zip(other).all(|(x, y)| x.v.spanless_eq(&y.v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpanlessEq for SyntaxModel {
|
impl SpanlessEq for SyntaxModel {
|
||||||
fn spanless_eq(&self, other: &SyntaxModel) -> bool {
|
fn spanless_eq(&self, other: &SyntaxModel) -> bool {
|
||||||
self.nodes.spanless_eq(&other.nodes)
|
self.nodes.spanless_eq(&other.nodes)
|
||||||
@ -130,6 +123,11 @@ impl SpanlessEq for Expr {
|
|||||||
(Expr::NamedTuple(a), Expr::NamedTuple(b)) => a.spanless_eq(b),
|
(Expr::NamedTuple(a), Expr::NamedTuple(b)) => a.spanless_eq(b),
|
||||||
(Expr::Tuple(a), Expr::Tuple(b)) => a.spanless_eq(b),
|
(Expr::Tuple(a), Expr::Tuple(b)) => a.spanless_eq(b),
|
||||||
(Expr::Object(a), Expr::Object(b)) => a.spanless_eq(b),
|
(Expr::Object(a), Expr::Object(b)) => a.spanless_eq(b),
|
||||||
|
(Expr::Neg(a), Expr::Neg(b)) => a.spanless_eq(&b),
|
||||||
|
(Expr::Add(a1, a2), Expr::Add(b1, b2)) => a1.spanless_eq(&b1) && a2.spanless_eq(&b2),
|
||||||
|
(Expr::Sub(a1, a2), Expr::Sub(b1, b2)) => a1.spanless_eq(&b1) && a2.spanless_eq(&b2),
|
||||||
|
(Expr::Mul(a1, a2), Expr::Mul(b1, b2)) => a1.spanless_eq(&b1) && a2.spanless_eq(&b2),
|
||||||
|
(Expr::Div(a1, a2), Expr::Div(b1, b2)) => a1.spanless_eq(&b1) && a2.spanless_eq(&b2),
|
||||||
(a, b) => a == b,
|
(a, b) => a == b,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +152,26 @@ impl SpanlessEq for Object {
|
|||||||
fn spanless_eq(&self, other: &Object) -> bool {
|
fn spanless_eq(&self, other: &Object) -> bool {
|
||||||
self.pairs.len() == other.pairs.len()
|
self.pairs.len() == other.pairs.len()
|
||||||
&& self.pairs.iter().zip(&other.pairs)
|
&& self.pairs.iter().zip(&other.pairs)
|
||||||
.all(|(x, y)| x.key.v == y.key.v && x.value.v.spanless_eq(&y.value.v))
|
.all(|(x, y)| x.v.key.v == y.v.key.v && x.v.value.v.spanless_eq(&y.v.value.v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SpanlessEq> SpanlessEq for Vec<T> {
|
||||||
|
fn spanless_eq(&self, other: &Vec<T>) -> bool {
|
||||||
|
self.len() == other.len()
|
||||||
|
&& self.iter().zip(other).all(|(x, y)| x.spanless_eq(&y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SpanlessEq> SpanlessEq for Spanned<T> {
|
||||||
|
fn spanless_eq(&self, other: &Spanned<T>) -> bool {
|
||||||
|
self.v.spanless_eq(&other.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SpanlessEq> SpanlessEq for Box<T> {
|
||||||
|
fn spanless_eq(&self, other: &Box<T>) -> bool {
|
||||||
|
(&**self).spanless_eq(&**other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,10 +78,18 @@ pub enum Token<'s> {
|
|||||||
ExprSize(Size),
|
ExprSize(Size),
|
||||||
/// A boolean in a function header: `true | false`.
|
/// A boolean in a function header: `true | false`.
|
||||||
ExprBool(bool),
|
ExprBool(bool),
|
||||||
/// A hex value in a function header: `#20d82a`
|
/// A hex value in a function header: `#20d82a`.
|
||||||
ExprHex(&'s str),
|
ExprHex(&'s str),
|
||||||
|
/// A plus in a function header, signifying the addition of expressions.
|
||||||
|
Plus,
|
||||||
|
/// A hyphen in a function header,
|
||||||
|
/// signifying the subtraction of expressions.
|
||||||
|
Hyphen,
|
||||||
|
/// A slash in a function header, signifying the division of expressions.
|
||||||
|
Slash,
|
||||||
|
|
||||||
/// A star in body-text.
|
/// A star. It can appear in a function header where it signifies the
|
||||||
|
/// multiplication of expressions or the body where it modifies the styling.
|
||||||
Star,
|
Star,
|
||||||
/// An underscore in body-text.
|
/// An underscore in body-text.
|
||||||
Underscore,
|
Underscore,
|
||||||
@ -125,6 +133,9 @@ impl<'s> Token<'s> {
|
|||||||
ExprSize(_) => "size",
|
ExprSize(_) => "size",
|
||||||
ExprBool(_) => "bool",
|
ExprBool(_) => "bool",
|
||||||
ExprHex(_) => "hex value",
|
ExprHex(_) => "hex value",
|
||||||
|
Plus => "plus",
|
||||||
|
Hyphen => "minus",
|
||||||
|
Slash => "slash",
|
||||||
Star => "star",
|
Star => "star",
|
||||||
Underscore => "underscore",
|
Underscore => "underscore",
|
||||||
Backslash => "backslash",
|
Backslash => "backslash",
|
||||||
@ -213,11 +224,19 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
',' if self.mode == Header => Comma,
|
',' if self.mode == Header => Comma,
|
||||||
'=' if self.mode == Header => Equals,
|
'=' if self.mode == Header => Equals,
|
||||||
|
|
||||||
|
// Expression operators.
|
||||||
|
'+' if self.mode == Header => Plus,
|
||||||
|
'-' if self.mode == Header => Hyphen,
|
||||||
|
'/' if self.mode == Header => Slash,
|
||||||
|
|
||||||
// String values.
|
// String values.
|
||||||
'"' if self.mode == Header => self.parse_string(),
|
'"' if self.mode == Header => self.parse_string(),
|
||||||
|
|
||||||
|
// Star serves a double purpose as a style modifier
|
||||||
|
// and a expression operator in the header.
|
||||||
|
'*' => Star,
|
||||||
|
|
||||||
// Style toggles.
|
// Style toggles.
|
||||||
'*' if self.mode == Body => Star,
|
|
||||||
'_' if self.mode == Body => Underscore,
|
'_' if self.mode == Body => Underscore,
|
||||||
'`' if self.mode == Body => self.parse_raw(),
|
'`' if self.mode == Body => self.parse_raw(),
|
||||||
|
|
||||||
@ -231,15 +250,20 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
c => {
|
c => {
|
||||||
let body = self.mode == Body;
|
let body = self.mode == Body;
|
||||||
|
|
||||||
|
let mut last_was_e = false;
|
||||||
let text = self.read_string_until(|n| {
|
let text = self.read_string_until(|n| {
|
||||||
match n {
|
let val = match n {
|
||||||
c if c.is_whitespace() => true,
|
c if c.is_whitespace() => true,
|
||||||
'[' | ']' | '/' => true,
|
'[' | ']' | '/' | '*' => true,
|
||||||
'\\' | '*' | '_' | '`' if body => true,
|
'\\' | '_' | '`' if body => true,
|
||||||
':' | '=' | ',' | '"' |
|
':' | '=' | ',' | '"' |
|
||||||
'(' | ')' | '{' | '}' if !body => true,
|
'(' | ')' | '{' | '}' if !body => true,
|
||||||
|
'+' | '-' if !body && !last_was_e => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
last_was_e = n == 'e' || n == 'E';
|
||||||
|
val
|
||||||
}, false, -(c.len_utf8() as isize), 0).0;
|
}, false, -(c.len_utf8() as isize), 0).0;
|
||||||
|
|
||||||
if self.mode == Header {
|
if self.mode == Header {
|
||||||
@ -411,6 +435,8 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Will read the input stream until the argument F evaluates to `true`
|
||||||
|
/// for the current character.
|
||||||
fn read_string_until<F>(
|
fn read_string_until<F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut f: F,
|
mut f: F,
|
||||||
@ -517,6 +543,10 @@ mod tests {
|
|||||||
ExprBool as Bool,
|
ExprBool as Bool,
|
||||||
ExprHex as Hex,
|
ExprHex as Hex,
|
||||||
Text as T,
|
Text as T,
|
||||||
|
Plus,
|
||||||
|
Hyphen as Min,
|
||||||
|
Star,
|
||||||
|
Slash,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@ -595,7 +625,7 @@ mod tests {
|
|||||||
t!(Body, "`]" => [Raw("]", false)]);
|
t!(Body, "`]" => [Raw("]", false)]);
|
||||||
t!(Body, "`\\``" => [Raw("\\`", true)]);
|
t!(Body, "`\\``" => [Raw("\\`", true)]);
|
||||||
t!(Body, "\\ " => [Backslash, S(0)]);
|
t!(Body, "\\ " => [Backslash, S(0)]);
|
||||||
t!(Header, "_*`" => [Invalid("_*`")]);
|
t!(Header, "_`" => [Invalid("_`")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -613,10 +643,13 @@ mod tests {
|
|||||||
t!(Header, "12e4%" => [Num(1200.0)]);
|
t!(Header, "12e4%" => [Num(1200.0)]);
|
||||||
t!(Header, "__main__" => [Id("__main__")]);
|
t!(Header, "__main__" => [Id("__main__")]);
|
||||||
t!(Header, ".func.box" => [Id(".func.box")]);
|
t!(Header, ".func.box" => [Id(".func.box")]);
|
||||||
t!(Header, "--arg, _b, _1" => [Id("--arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1")]);
|
t!(Header, "arg, _b, _1" => [Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1")]);
|
||||||
t!(Header, "12_pt, 12pt" => [Invalid("12_pt"), Comma, S(0), Sz(Size::pt(12.0))]);
|
t!(Header, "12_pt, 12pt" => [Invalid("12_pt"), Comma, S(0), Sz(Size::pt(12.0))]);
|
||||||
t!(Header, "1e5in" => [Sz(Size::inches(100000.0))]);
|
t!(Header, "1e5in" => [Sz(Size::inches(100000.0))]);
|
||||||
t!(Header, "2.3cm" => [Sz(Size::cm(2.3))]);
|
t!(Header, "2.3cm" => [Sz(Size::cm(2.3))]);
|
||||||
|
t!(Header, "12e-3in" => [Sz(Size::inches(12e-3))]);
|
||||||
|
t!(Header, "6.1cm + 4pt,a=1*2" => [Sz(Size::cm(6.1)), S(0), Plus, S(0), Sz(Size::pt(4.0)), Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0)]);
|
||||||
|
t!(Header, "(5 - 1) / 2.1" => [LP, Num(5.0), S(0), Min, S(0), Num(1.0), RP, S(0), Slash, S(0), Num(2.1)]);
|
||||||
t!(Header, "02.4mm" => [Sz(Size::mm(2.4))]);
|
t!(Header, "02.4mm" => [Sz(Size::mm(2.4))]);
|
||||||
t!(Header, "2.4.cm" => [Invalid("2.4.cm")]);
|
t!(Header, "2.4.cm" => [Invalid("2.4.cm")]);
|
||||||
t!(Header, "(1,2)" => [LP, Num(1.0), Comma, Num(2.0), RP]);
|
t!(Header, "(1,2)" => [LP, Num(1.0), Comma, Num(2.0), RP]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user