mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Final touches
This commit is contained in:
parent
75fffc1f9b
commit
38c5c36241
@ -5,11 +5,10 @@ authors = ["The Typst Project Developers"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["cli", "fs", "layout-cache", "parse-cache"]
|
default = ["cli", "fs", "layout-cache"]
|
||||||
cli = ["anyhow", "codespan-reporting", "fs", "pico-args", "same-file"]
|
cli = ["anyhow", "codespan-reporting", "fs", "pico-args", "same-file"]
|
||||||
fs = ["dirs", "memmap2", "same-file", "walkdir"]
|
fs = ["dirs", "memmap2", "same-file", "walkdir"]
|
||||||
layout-cache = ["rand"]
|
layout-cache = ["rand"]
|
||||||
parse-cache = []
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
# Faster compilation
|
# Faster compilation
|
||||||
|
@ -44,11 +44,11 @@ fn bench_scan(iai: &mut Iai) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_tokenize(iai: &mut Iai) {
|
fn bench_tokenize(iai: &mut Iai) {
|
||||||
iai.run(|| Tokens::new(black_box(&SRC), black_box(TokenMode::Markup)).count());
|
iai.run(|| Tokens::new(black_box(SRC), black_box(TokenMode::Markup)).count());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_parse(iai: &mut Iai) {
|
fn bench_parse(iai: &mut Iai) {
|
||||||
iai.run(|| parse(&SRC));
|
iai.run(|| parse(SRC));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_eval(iai: &mut Iai) {
|
fn bench_eval(iai: &mut Iai) {
|
||||||
|
@ -172,5 +172,6 @@ mod tests {
|
|||||||
|
|
||||||
// Scoping.
|
// Scoping.
|
||||||
test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
|
test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
|
||||||
|
test("[#let x = 1]#x", &["x"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,11 +398,11 @@ impl Eval for CallArgs {
|
|||||||
value: Spanned::new(expr.eval(ctx)?, expr.span()),
|
value: Spanned::new(expr.eval(ctx)?, expr.span()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CallArg::Named(x) => {
|
CallArg::Named(named) => {
|
||||||
items.push(Arg {
|
items.push(Arg {
|
||||||
span,
|
span,
|
||||||
name: Some(x.name().take().into()),
|
name: Some(named.name().take().into()),
|
||||||
value: Spanned::new(x.expr().eval(ctx)?, x.expr().span()),
|
value: Spanned::new(named.expr().eval(ctx)?, named.expr().span()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CallArg::Spread(expr) => match expr.eval(ctx)? {
|
CallArg::Spread(expr) => match expr.eval(ctx)? {
|
||||||
@ -511,8 +511,8 @@ impl Eval for WithExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let wrapped =
|
let callee = self.callee();
|
||||||
self.callee().eval(ctx)?.cast::<Function>().at(self.callee().span())?;
|
let wrapped = callee.eval(ctx)?.cast::<Function>().at(callee.span())?;
|
||||||
let applied = self.args().eval(ctx)?;
|
let applied = self.args().eval(ctx)?;
|
||||||
|
|
||||||
let name = wrapped.name().cloned();
|
let name = wrapped.name().cloned();
|
||||||
@ -529,7 +529,7 @@ impl Eval for LetExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let value = match &self.init() {
|
let value = match self.init() {
|
||||||
Some(expr) => expr.eval(ctx)?,
|
Some(expr) => expr.eval(ctx)?,
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
};
|
};
|
||||||
@ -542,15 +542,10 @@ impl Eval for IfExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let condition = self
|
let condition = self.condition();
|
||||||
.condition()
|
if condition.eval(ctx)?.cast::<bool>().at(condition.span())? {
|
||||||
.eval(ctx)?
|
|
||||||
.cast::<bool>()
|
|
||||||
.at(self.condition().span())?;
|
|
||||||
|
|
||||||
if condition {
|
|
||||||
self.if_body().eval(ctx)
|
self.if_body().eval(ctx)
|
||||||
} else if let Some(else_body) = &self.else_body() {
|
} else if let Some(else_body) = self.else_body() {
|
||||||
else_body.eval(ctx)
|
else_body.eval(ctx)
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
@ -564,14 +559,11 @@ impl Eval for WhileExpr {
|
|||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let mut output = Value::None;
|
let mut output = Value::None;
|
||||||
|
|
||||||
while self
|
let condition = self.condition();
|
||||||
.condition()
|
while condition.eval(ctx)?.cast::<bool>().at(condition.span())? {
|
||||||
.eval(ctx)?
|
let body = self.body();
|
||||||
.cast::<bool>()
|
let value = body.eval(ctx)?;
|
||||||
.at(self.condition().span())?
|
output = ops::join(output, value).at(body.span())?;
|
||||||
{
|
|
||||||
let value = self.body().eval(ctx)?;
|
|
||||||
output = ops::join(output, value).at(self.body().span())?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
@ -597,7 +589,7 @@ impl Eval for ForExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.scopes.exit();
|
ctx.scopes.exit();
|
||||||
Ok(output)
|
return Ok(output);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,18 +599,20 @@ impl Eval for ForExpr {
|
|||||||
let value = pattern.value().take();
|
let value = pattern.value().take();
|
||||||
|
|
||||||
match (key, value, iter) {
|
match (key, value, iter) {
|
||||||
(None, v, Value::Str(string)) => iter!(for (v => value) in string.iter()),
|
(None, v, Value::Str(string)) => {
|
||||||
|
iter!(for (v => value) in string.iter());
|
||||||
|
}
|
||||||
(None, v, Value::Array(array)) => {
|
(None, v, Value::Array(array)) => {
|
||||||
iter!(for (v => value) in array.into_iter())
|
iter!(for (v => value) in array.into_iter());
|
||||||
}
|
}
|
||||||
(Some(i), v, Value::Array(array)) => {
|
(Some(i), v, Value::Array(array)) => {
|
||||||
iter!(for (i => idx, v => value) in array.into_iter().enumerate())
|
iter!(for (i => idx, v => value) in array.into_iter().enumerate());
|
||||||
}
|
}
|
||||||
(None, v, Value::Dict(dict)) => {
|
(None, v, Value::Dict(dict)) => {
|
||||||
iter!(for (v => value) in dict.into_iter().map(|p| p.1))
|
iter!(for (v => value) in dict.into_iter().map(|p| p.1));
|
||||||
}
|
}
|
||||||
(Some(k), v, Value::Dict(dict)) => {
|
(Some(k), v, Value::Dict(dict)) => {
|
||||||
iter!(for (k => key, v => value) in dict.into_iter())
|
iter!(for (k => key, v => value) in dict.into_iter());
|
||||||
}
|
}
|
||||||
(_, _, Value::Str(_)) => {
|
(_, _, Value::Str(_)) => {
|
||||||
bail!(pattern.span(), "mismatched pattern");
|
bail!(pattern.span(), "mismatched pattern");
|
||||||
@ -634,9 +628,9 @@ impl Eval for ImportExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let path = self.path().eval(ctx)?.cast::<Str>().at(self.path().span())?;
|
let path = self.path();
|
||||||
|
let resolved = path.eval(ctx)?.cast::<Str>().at(path.span())?;
|
||||||
let file = ctx.import(&path, self.path().span())?;
|
let file = ctx.import(&resolved, path.span())?;
|
||||||
let module = &ctx.modules[&file];
|
let module = &ctx.modules[&file];
|
||||||
|
|
||||||
match self.imports() {
|
match self.imports() {
|
||||||
@ -664,12 +658,10 @@ impl Eval for IncludeExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let path_node = self.path();
|
let path = self.path();
|
||||||
let path = path_node.eval(ctx)?.cast::<Str>().at(path_node.span())?;
|
let resolved = path.eval(ctx)?.cast::<Str>().at(path.span())?;
|
||||||
|
let file = ctx.import(&resolved, path.span())?;
|
||||||
let file = ctx.import(&path, path_node.span())?;
|
|
||||||
let module = &ctx.modules[&file];
|
let module = &ctx.modules[&file];
|
||||||
|
|
||||||
Ok(Value::Template(module.template.clone()))
|
Ok(Value::Template(module.template.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ impl Walk for MarkupNode {
|
|||||||
Self::Emph => ctx.template.modify(|s| s.text_mut().emph.flip()),
|
Self::Emph => ctx.template.modify(|s| s.text_mut().emph.flip()),
|
||||||
Self::Text(text) => ctx.template.text(text),
|
Self::Text(text) => ctx.template.text(text),
|
||||||
Self::Raw(raw) => raw.walk(ctx)?,
|
Self::Raw(raw) => raw.walk(ctx)?,
|
||||||
|
Self::Math(math) => math.walk(ctx)?,
|
||||||
Self::Heading(heading) => heading.walk(ctx)?,
|
Self::Heading(heading) => heading.walk(ctx)?,
|
||||||
Self::List(list) => list.walk(ctx)?,
|
Self::List(list) => list.walk(ctx)?,
|
||||||
Self::Enum(enum_) => enum_.walk(ctx)?,
|
Self::Enum(enum_) => enum_.walk(ctx)?,
|
||||||
@ -67,6 +68,22 @@ impl Walk for RawNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Walk for MathNode {
|
||||||
|
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||||
|
if self.display {
|
||||||
|
ctx.template.parbreak();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.template.monospace(self.formula.trim());
|
||||||
|
|
||||||
|
if self.display {
|
||||||
|
ctx.template.parbreak();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Walk for HeadingNode {
|
impl Walk for HeadingNode {
|
||||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||||
let level = self.level();
|
let level = self.level();
|
||||||
|
@ -16,8 +16,8 @@ use crate::syntax::ast::{Associativity, BinOp, UnOp};
|
|||||||
use crate::syntax::{ErrorPos, Green, GreenNode, NodeKind};
|
use crate::syntax::{ErrorPos, Green, GreenNode, NodeKind};
|
||||||
|
|
||||||
/// Parse a source file.
|
/// Parse a source file.
|
||||||
pub fn parse(source: &str) -> Rc<GreenNode> {
|
pub fn parse(src: &str) -> Rc<GreenNode> {
|
||||||
let mut p = Parser::new(source);
|
let mut p = Parser::new(src);
|
||||||
markup(&mut p);
|
markup(&mut p);
|
||||||
match p.finish().into_iter().next() {
|
match p.finish().into_iter().next() {
|
||||||
Some(Green::Node(node)) => node,
|
Some(Green::Node(node)) => node,
|
||||||
@ -93,16 +93,17 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
|
|||||||
| NodeKind::Strong
|
| NodeKind::Strong
|
||||||
| NodeKind::Linebreak
|
| NodeKind::Linebreak
|
||||||
| NodeKind::Raw(_)
|
| NodeKind::Raw(_)
|
||||||
|
| NodeKind::Math(_)
|
||||||
| NodeKind::UnicodeEscape(_) => {
|
| NodeKind::UnicodeEscape(_) => {
|
||||||
p.eat();
|
p.eat();
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeKind::Eq if *at_start => heading(p),
|
NodeKind::Eq if *at_start => heading(p),
|
||||||
NodeKind::ListBullet if *at_start => list_node(p),
|
NodeKind::Minus if *at_start => list_node(p),
|
||||||
NodeKind::EnumNumbering(_) if *at_start => enum_node(p),
|
NodeKind::EnumNumbering(_) if *at_start => enum_node(p),
|
||||||
|
|
||||||
// Line-based markup that is not currently at the start of the line.
|
// Line-based markup that is not currently at the start of the line.
|
||||||
NodeKind::Eq | NodeKind::ListBullet | NodeKind::EnumNumbering(_) => {
|
NodeKind::Eq | NodeKind::Minus | NodeKind::EnumNumbering(_) => {
|
||||||
p.convert(NodeKind::Text(p.peek_src().into()));
|
p.convert(NodeKind::Text(p.peek_src().into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ fn heading(p: &mut Parser) {
|
|||||||
/// Parse a single list item.
|
/// Parse a single list item.
|
||||||
fn list_node(p: &mut Parser) {
|
fn list_node(p: &mut Parser) {
|
||||||
p.perform(NodeKind::List, |p| {
|
p.perform(NodeKind::List, |p| {
|
||||||
p.eat_assert(&NodeKind::ListBullet);
|
p.eat_assert(&NodeKind::Minus);
|
||||||
let column = p.column(p.prev_end());
|
let column = p.column(p.prev_end());
|
||||||
markup_indented(p, column);
|
markup_indented(p, column);
|
||||||
});
|
});
|
||||||
@ -193,10 +194,7 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult {
|
|||||||
loop {
|
loop {
|
||||||
// Exclamation mark, parenthesis or bracket means this is a function
|
// Exclamation mark, parenthesis or bracket means this is a function
|
||||||
// call.
|
// call.
|
||||||
if matches!(
|
if let Some(NodeKind::LeftParen | NodeKind::LeftBracket) = p.peek_direct() {
|
||||||
p.peek_direct(),
|
|
||||||
Some(NodeKind::LeftParen | NodeKind::LeftBracket)
|
|
||||||
) {
|
|
||||||
call(p, marker)?;
|
call(p, marker)?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -241,7 +239,6 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
|
|||||||
match p.peek() {
|
match p.peek() {
|
||||||
// Things that start with an identifier.
|
// Things that start with an identifier.
|
||||||
Some(NodeKind::Ident(_)) => {
|
Some(NodeKind::Ident(_)) => {
|
||||||
// Start closure params.
|
|
||||||
let marker = p.marker();
|
let marker = p.marker();
|
||||||
p.eat();
|
p.eat();
|
||||||
|
|
||||||
@ -364,9 +361,10 @@ enum CollectionKind {
|
|||||||
/// Returns the length of the collection and whether the literal contained any
|
/// Returns the length of the collection and whether the literal contained any
|
||||||
/// commas.
|
/// commas.
|
||||||
fn collection(p: &mut Parser) -> (CollectionKind, usize) {
|
fn collection(p: &mut Parser) -> (CollectionKind, usize) {
|
||||||
let mut items = 0;
|
|
||||||
let mut kind = CollectionKind::Positional;
|
let mut kind = CollectionKind::Positional;
|
||||||
|
let mut items = 0;
|
||||||
let mut can_group = true;
|
let mut can_group = true;
|
||||||
|
let mut error = false;
|
||||||
let mut missing_coma: Option<Marker> = None;
|
let mut missing_coma: Option<Marker> = None;
|
||||||
|
|
||||||
while !p.eof() {
|
while !p.eof() {
|
||||||
@ -393,12 +391,14 @@ fn collection(p: &mut Parser) -> (CollectionKind, usize) {
|
|||||||
if p.eat_if(&NodeKind::Comma) {
|
if p.eat_if(&NodeKind::Comma) {
|
||||||
can_group = false;
|
can_group = false;
|
||||||
} else {
|
} else {
|
||||||
missing_coma = Some(p.marker());
|
missing_coma = Some(p.trivia_start());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if can_group && items == 1 {
|
if error || (can_group && items == 1) {
|
||||||
kind = CollectionKind::Group;
|
kind = CollectionKind::Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,7 +467,7 @@ fn params(p: &mut Parser, marker: Marker) {
|
|||||||
NodeKind::Named | NodeKind::Comma | NodeKind::Ident(_) => Ok(()),
|
NodeKind::Named | NodeKind::Comma | NodeKind::Ident(_) => Ok(()),
|
||||||
NodeKind::Spread
|
NodeKind::Spread
|
||||||
if matches!(
|
if matches!(
|
||||||
x.children().last().map(|x| x.kind()),
|
x.children().last().map(|child| child.kind()),
|
||||||
Some(&NodeKind::Ident(_))
|
Some(&NodeKind::Ident(_))
|
||||||
) =>
|
) =>
|
||||||
{
|
{
|
||||||
|
@ -52,6 +52,17 @@ impl<'s> Parser<'s> {
|
|||||||
Marker(self.children.len())
|
Marker(self.children.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a markup right before the trailing trivia.
|
||||||
|
pub fn trivia_start(&self) -> Marker {
|
||||||
|
let count = self
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.take_while(|node| self.is_trivia(node.kind()))
|
||||||
|
.count();
|
||||||
|
Marker(self.children.len() - count)
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform a subparse that wraps its result in a node with the given kind.
|
/// Perform a subparse that wraps its result in a node with the given kind.
|
||||||
pub fn perform<F, T>(&mut self, kind: NodeKind, f: F) -> T
|
pub fn perform<F, T>(&mut self, kind: NodeKind, f: F) -> T
|
||||||
where
|
where
|
||||||
@ -66,7 +77,7 @@ impl<'s> Parser<'s> {
|
|||||||
// Trailing trivia should not be wrapped into the new node.
|
// Trailing trivia should not be wrapped into the new node.
|
||||||
let idx = self.children.len();
|
let idx = self.children.len();
|
||||||
self.children.push(Green::default());
|
self.children.push(Green::default());
|
||||||
self.children.extend(children.drain(until ..));
|
self.children.extend(children.drain(until.0 ..));
|
||||||
self.children[idx] = GreenNode::with_children(kind, children).into();
|
self.children[idx] = GreenNode::with_children(kind, children).into();
|
||||||
} else {
|
} else {
|
||||||
self.children.push(GreenNode::with_children(kind, children).into());
|
self.children.push(GreenNode::with_children(kind, children).into());
|
||||||
@ -238,7 +249,7 @@ impl<'s> Parser<'s> {
|
|||||||
// Rescan the peeked token if the mode changed.
|
// Rescan the peeked token if the mode changed.
|
||||||
if rescan {
|
if rescan {
|
||||||
if group_mode == TokenMode::Code {
|
if group_mode == TokenMode::Code {
|
||||||
self.children.truncate(self.trivia_start());
|
self.children.truncate(self.trivia_start().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tokens.jump(self.prev_end());
|
self.tokens.jump(self.prev_end());
|
||||||
@ -290,17 +301,6 @@ impl<'s> Parser<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the index in the children list where trailing trivia starts.
|
|
||||||
fn trivia_start(&self) -> usize {
|
|
||||||
self.children.len()
|
|
||||||
- self
|
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.take_while(|node| self.is_trivia(node.kind()))
|
|
||||||
.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the active group must end at a newline.
|
/// Whether the active group must end at a newline.
|
||||||
fn stop_at_newline(&self) -> bool {
|
fn stop_at_newline(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
@ -350,7 +350,7 @@ impl Parser<'_> {
|
|||||||
/// Add an error that the `thing` was expected at the end of the last
|
/// Add an error that the `thing` was expected at the end of the last
|
||||||
/// non-trivia token.
|
/// non-trivia token.
|
||||||
pub fn expected_at(&mut self, thing: &str) {
|
pub fn expected_at(&mut self, thing: &str) {
|
||||||
Marker(self.trivia_start()).expected(self, thing);
|
self.trivia_start().expected(self, thing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +374,7 @@ impl Marker {
|
|||||||
/// with the given `kind`.
|
/// with the given `kind`.
|
||||||
pub fn end(self, p: &mut Parser, kind: NodeKind) {
|
pub fn end(self, p: &mut Parser, kind: NodeKind) {
|
||||||
let until = p.trivia_start();
|
let until = p.trivia_start();
|
||||||
let children = p.children.drain(self.0 .. until).collect();
|
let children = p.children.drain(self.0 .. until.0).collect();
|
||||||
p.children
|
p.children
|
||||||
.insert(self.0, GreenNode::with_children(kind, children).into());
|
.insert(self.0, GreenNode::with_children(kind, children).into());
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::{is_ident, is_newline, Scanner};
|
use super::{is_ident, is_newline, Scanner};
|
||||||
use crate::syntax::RawData;
|
use crate::syntax::ast::RawNode;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Resolve all escape sequences in a string.
|
/// Resolve all escape sequences in a string.
|
||||||
@ -46,21 +46,19 @@ pub fn resolve_hex(sequence: &str) -> Option<char> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the language tag and trims the raw text.
|
/// Resolve the language tag and trims the raw text.
|
||||||
pub fn resolve_raw(column: usize, backticks: u8, text: &str) -> RawData {
|
pub fn resolve_raw(column: usize, backticks: usize, text: &str) -> RawNode {
|
||||||
if backticks > 1 {
|
if backticks > 1 {
|
||||||
let (tag, inner) = split_at_lang_tag(text);
|
let (tag, inner) = split_at_lang_tag(text);
|
||||||
let (text, block) = trim_and_split_raw(column, inner);
|
let (text, block) = trim_and_split_raw(column, inner);
|
||||||
RawData {
|
RawNode {
|
||||||
lang: is_ident(tag).then(|| tag.into()),
|
lang: is_ident(tag).then(|| tag.into()),
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
backticks,
|
|
||||||
block,
|
block,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
RawData {
|
RawNode {
|
||||||
lang: None,
|
lang: None,
|
||||||
text: split_lines(text).join("\n").into(),
|
text: split_lines(text).join("\n").into(),
|
||||||
backticks,
|
|
||||||
block: false,
|
block: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,7 +179,7 @@ mod tests {
|
|||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(
|
fn test(
|
||||||
column: usize,
|
column: usize,
|
||||||
backticks: u8,
|
backticks: usize,
|
||||||
raw: &str,
|
raw: &str,
|
||||||
lang: Option<&str>,
|
lang: Option<&str>,
|
||||||
text: &str,
|
text: &str,
|
||||||
|
@ -5,7 +5,8 @@ use super::{
|
|||||||
Scanner,
|
Scanner,
|
||||||
};
|
};
|
||||||
use crate::geom::{AngularUnit, LengthUnit};
|
use crate::geom::{AngularUnit, LengthUnit};
|
||||||
use crate::syntax::*;
|
use crate::syntax::ast::{MathNode, RawNode};
|
||||||
|
use crate::syntax::{ErrorPos, NodeKind};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// An iterator over the tokens of a string of source code.
|
/// An iterator over the tokens of a string of source code.
|
||||||
@ -26,8 +27,8 @@ pub enum TokenMode {
|
|||||||
impl<'s> Tokens<'s> {
|
impl<'s> Tokens<'s> {
|
||||||
/// Create a new token iterator with the given mode.
|
/// Create a new token iterator with the given mode.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(source: &'s str, mode: TokenMode) -> Self {
|
pub fn new(src: &'s str, mode: TokenMode) -> Self {
|
||||||
Self { s: Scanner::new(source), mode }
|
Self { s: Scanner::new(src), mode }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current token mode.
|
/// Get the current token mode.
|
||||||
@ -254,7 +255,7 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c if c.is_whitespace() => NodeKind::Linebreak,
|
c if c.is_whitespace() => NodeKind::Linebreak,
|
||||||
_ => NodeKind::Text("\\".into()),
|
_ => NodeKind::Text('\\'.into()),
|
||||||
},
|
},
|
||||||
None => NodeKind::Linebreak,
|
None => NodeKind::Linebreak,
|
||||||
}
|
}
|
||||||
@ -281,7 +282,7 @@ impl<'s> Tokens<'s> {
|
|||||||
NodeKind::EnDash
|
NodeKind::EnDash
|
||||||
}
|
}
|
||||||
} else if self.s.check_or(true, char::is_whitespace) {
|
} else if self.s.check_or(true, char::is_whitespace) {
|
||||||
NodeKind::ListBullet
|
NodeKind::Minus
|
||||||
} else {
|
} else {
|
||||||
NodeKind::Text("-".into())
|
NodeKind::Text("-".into())
|
||||||
}
|
}
|
||||||
@ -310,16 +311,15 @@ impl<'s> Tokens<'s> {
|
|||||||
let column = self.s.column(self.s.index() - 1);
|
let column = self.s.column(self.s.index() - 1);
|
||||||
|
|
||||||
let mut backticks = 1;
|
let mut backticks = 1;
|
||||||
while self.s.eat_if('`') && backticks < u8::MAX {
|
while self.s.eat_if('`') {
|
||||||
backticks += 1;
|
backticks += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case for empty inline block.
|
// Special case for empty inline block.
|
||||||
if backticks == 2 {
|
if backticks == 2 {
|
||||||
return NodeKind::Raw(Rc::new(RawData {
|
return NodeKind::Raw(Rc::new(RawNode {
|
||||||
text: EcoString::new(),
|
text: EcoString::new(),
|
||||||
lang: None,
|
lang: None,
|
||||||
backticks: 1,
|
|
||||||
block: false,
|
block: false,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -389,7 +389,7 @@ impl<'s> Tokens<'s> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if terminated {
|
if terminated {
|
||||||
NodeKind::Math(Rc::new(MathData {
|
NodeKind::Math(Rc::new(MathNode {
|
||||||
formula: self.s.get(start .. end).into(),
|
formula: self.s.get(start .. end).into(),
|
||||||
display,
|
display,
|
||||||
}))
|
}))
|
||||||
@ -429,9 +429,7 @@ impl<'s> Tokens<'s> {
|
|||||||
|
|
||||||
// Read the exponent.
|
// Read the exponent.
|
||||||
if self.s.eat_if('e') || self.s.eat_if('E') {
|
if self.s.eat_if('e') || self.s.eat_if('E') {
|
||||||
if !self.s.eat_if('+') {
|
let _ = self.s.eat_if('+') || self.s.eat_if('-');
|
||||||
self.s.eat_if('-');
|
|
||||||
}
|
|
||||||
self.s.eat_while(|c| c.is_ascii_digit());
|
self.s.eat_while(|c| c.is_ascii_digit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,6 +481,7 @@ impl<'s> Tokens<'s> {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if self.s.eat_if('"') {
|
if self.s.eat_if('"') {
|
||||||
NodeKind::Str(string)
|
NodeKind::Str(string)
|
||||||
} else {
|
} else {
|
||||||
@ -567,17 +566,16 @@ mod tests {
|
|||||||
NodeKind::Error(pos, message.into())
|
NodeKind::Error(pos, message.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Raw(text: &str, lang: Option<&str>, backticks_left: u8, block: bool) -> NodeKind {
|
fn Raw(text: &str, lang: Option<&str>, block: bool) -> NodeKind {
|
||||||
NodeKind::Raw(Rc::new(RawData {
|
NodeKind::Raw(Rc::new(RawNode {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
lang: lang.map(Into::into),
|
lang: lang.map(Into::into),
|
||||||
backticks: backticks_left,
|
|
||||||
block,
|
block,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Math(formula: &str, display: bool) -> NodeKind {
|
fn Math(formula: &str, display: bool) -> NodeKind {
|
||||||
NodeKind::Math(Rc::new(MathData { formula: formula.into(), display }))
|
NodeKind::Math(Rc::new(MathNode { formula: formula.into(), display }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Str(string: &str) -> NodeKind {
|
fn Str(string: &str) -> NodeKind {
|
||||||
@ -655,13 +653,13 @@ mod tests {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Test with each applicable suffix.
|
// Test with each applicable suffix.
|
||||||
for (block, mode, suffix, token) in suffixes {
|
for &(block, mode, suffix, ref token) in suffixes {
|
||||||
let src = $src;
|
let src = $src;
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let blocks = BLOCKS;
|
let blocks = BLOCKS;
|
||||||
$(let blocks = $blocks;)?
|
$(let blocks = $blocks;)?
|
||||||
assert!(!blocks.contains(|c| !BLOCKS.contains(c)));
|
assert!(!blocks.contains(|c| !BLOCKS.contains(c)));
|
||||||
if (mode.is_none() || mode == &Some($mode)) && blocks.contains(*block) {
|
if (mode.is_none() || mode == Some($mode)) && blocks.contains(block) {
|
||||||
t!(@$mode: format!("{}{}", src, suffix) => $($token,)* token);
|
t!(@$mode: format!("{}{}", src, suffix) => $($token,)* token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -790,7 +788,7 @@ mod tests {
|
|||||||
t!(Markup: "~" => NonBreakingSpace);
|
t!(Markup: "~" => NonBreakingSpace);
|
||||||
t!(Markup[" "]: r"\" => Linebreak);
|
t!(Markup[" "]: r"\" => Linebreak);
|
||||||
t!(Markup["a "]: r"a--" => Text("a"), EnDash);
|
t!(Markup["a "]: r"a--" => Text("a"), EnDash);
|
||||||
t!(Markup["a1/"]: "- " => ListBullet, Space(0));
|
t!(Markup["a1/"]: "- " => Minus, Space(0));
|
||||||
t!(Markup[" "]: "." => EnumNumbering(None));
|
t!(Markup[" "]: "." => EnumNumbering(None));
|
||||||
t!(Markup[" "]: "1." => EnumNumbering(Some(1)));
|
t!(Markup[" "]: "1." => EnumNumbering(Some(1)));
|
||||||
t!(Markup[" "]: "1.a" => Text("1."), Text("a"));
|
t!(Markup[" "]: "1.a" => Text("1."), Text("a"));
|
||||||
@ -867,22 +865,22 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_tokenize_raw_blocks() {
|
fn test_tokenize_raw_blocks() {
|
||||||
// Test basic raw block.
|
// Test basic raw block.
|
||||||
t!(Markup: "``" => Raw("", None, 1, false));
|
t!(Markup: "``" => Raw("", None, false));
|
||||||
t!(Markup: "`raw`" => Raw("raw", None, 1, false));
|
t!(Markup: "`raw`" => Raw("raw", None, false));
|
||||||
t!(Markup[""]: "`]" => Error(End, "expected 1 backtick"));
|
t!(Markup[""]: "`]" => Error(End, "expected 1 backtick"));
|
||||||
|
|
||||||
// Test special symbols in raw block.
|
// Test special symbols in raw block.
|
||||||
t!(Markup: "`[brackets]`" => Raw("[brackets]", None, 1, false));
|
t!(Markup: "`[brackets]`" => Raw("[brackets]", None, false));
|
||||||
t!(Markup[""]: r"`\`` " => Raw(r"\", None, 1, false), Error(End, "expected 1 backtick"));
|
t!(Markup[""]: r"`\`` " => Raw(r"\", None, false), Error(End, "expected 1 backtick"));
|
||||||
|
|
||||||
// Test separated closing backticks.
|
// Test separated closing backticks.
|
||||||
t!(Markup: "```not `y`e`t```" => Raw("`y`e`t", Some("not"), 3, false));
|
t!(Markup: "```not `y`e`t```" => Raw("`y`e`t", Some("not"), false));
|
||||||
|
|
||||||
// Test more backticks.
|
// Test more backticks.
|
||||||
t!(Markup: "``nope``" => Raw("", None, 1, false), Text("nope"), Raw("", None, 1, false));
|
t!(Markup: "``nope``" => Raw("", None, false), Text("nope"), Raw("", None, false));
|
||||||
t!(Markup: "````🚀````" => Raw("", None, 4, false));
|
t!(Markup: "````🚀````" => Raw("", None, false));
|
||||||
t!(Markup[""]: "`````👩🚀````noend" => Error(End, "expected 5 backticks"));
|
t!(Markup[""]: "`````👩🚀````noend" => Error(End, "expected 5 backticks"));
|
||||||
t!(Markup[""]: "````raw``````" => Raw("", Some("raw"), 4, false), Raw("", None, 1, false));
|
t!(Markup[""]: "````raw``````" => Raw("", Some("raw"), false), Raw("", None, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -144,6 +144,7 @@ impl SourceFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The file's abstract syntax tree.
|
||||||
pub fn ast(&self) -> TypResult<Markup> {
|
pub fn ast(&self) -> TypResult<Markup> {
|
||||||
let red = RedNode::from_root(self.root.clone(), self.id);
|
let red = RedNode::from_root(self.root.clone(), self.id);
|
||||||
let errors = red.errors();
|
let errors = red.errors();
|
||||||
|
@ -68,11 +68,8 @@ impl Markup {
|
|||||||
NodeKind::EnDash => Some(MarkupNode::Text("\u{2013}".into())),
|
NodeKind::EnDash => Some(MarkupNode::Text("\u{2013}".into())),
|
||||||
NodeKind::EmDash => Some(MarkupNode::Text("\u{2014}".into())),
|
NodeKind::EmDash => Some(MarkupNode::Text("\u{2014}".into())),
|
||||||
NodeKind::NonBreakingSpace => Some(MarkupNode::Text("\u{00A0}".into())),
|
NodeKind::NonBreakingSpace => Some(MarkupNode::Text("\u{00A0}".into())),
|
||||||
NodeKind::Raw(raw) => Some(MarkupNode::Raw(RawNode {
|
NodeKind::Math(math) => Some(MarkupNode::Math(math.as_ref().clone())),
|
||||||
block: raw.block,
|
NodeKind::Raw(raw) => Some(MarkupNode::Raw(raw.as_ref().clone())),
|
||||||
lang: raw.lang.clone(),
|
|
||||||
text: raw.text.clone(),
|
|
||||||
})),
|
|
||||||
NodeKind::Heading => node.cast().map(MarkupNode::Heading),
|
NodeKind::Heading => node.cast().map(MarkupNode::Heading),
|
||||||
NodeKind::List => node.cast().map(MarkupNode::List),
|
NodeKind::List => node.cast().map(MarkupNode::List),
|
||||||
NodeKind::Enum => node.cast().map(MarkupNode::Enum),
|
NodeKind::Enum => node.cast().map(MarkupNode::Enum),
|
||||||
@ -98,6 +95,8 @@ pub enum MarkupNode {
|
|||||||
Text(EcoString),
|
Text(EcoString),
|
||||||
/// A raw block with optional syntax highlighting: `` `...` ``.
|
/// A raw block with optional syntax highlighting: `` `...` ``.
|
||||||
Raw(RawNode),
|
Raw(RawNode),
|
||||||
|
/// A math formula: `$a^2 = b^2 + c^2$`.
|
||||||
|
Math(MathNode),
|
||||||
/// A section heading: `= Introduction`.
|
/// A section heading: `= Introduction`.
|
||||||
Heading(HeadingNode),
|
Heading(HeadingNode),
|
||||||
/// An item in an unordered list: `- ...`.
|
/// An item in an unordered list: `- ...`.
|
||||||
@ -121,6 +120,16 @@ pub struct RawNode {
|
|||||||
pub block: bool,
|
pub block: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A math formula: `$a^2 + b^2 = c^2$`.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct MathNode {
|
||||||
|
/// The formula between the dollars / brackets.
|
||||||
|
pub formula: EcoString,
|
||||||
|
/// Whether the formula is display-level, that is, it is surrounded by
|
||||||
|
/// `$[..]$`.
|
||||||
|
pub display: bool,
|
||||||
|
}
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A section heading: `= Introduction`.
|
/// A section heading: `= Introduction`.
|
||||||
HeadingNode: Heading
|
HeadingNode: Heading
|
||||||
@ -133,12 +142,8 @@ impl HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The section depth (numer of equals signs).
|
/// The section depth (numer of equals signs).
|
||||||
pub fn level(&self) -> u8 {
|
pub fn level(&self) -> usize {
|
||||||
self.0
|
self.0.children().filter(|n| n.kind() == &NodeKind::Eq).count()
|
||||||
.children()
|
|
||||||
.filter(|n| n.kind() == &NodeKind::Eq)
|
|
||||||
.count()
|
|
||||||
.min(u8::MAX.into()) as u8
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use std::rc::Rc;
|
|||||||
pub use pretty::*;
|
pub use pretty::*;
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
|
|
||||||
use self::ast::TypedNode;
|
use self::ast::{MathNode, RawNode, TypedNode};
|
||||||
use crate::diag::Error;
|
use crate::diag::Error;
|
||||||
use crate::geom::{AngularUnit, LengthUnit};
|
use crate::geom::{AngularUnit, LengthUnit};
|
||||||
use crate::source::SourceId;
|
use crate::source::SourceId;
|
||||||
@ -178,7 +178,7 @@ impl From<GreenData> for Green {
|
|||||||
|
|
||||||
/// A owned wrapper for a green node with span information.
|
/// A owned wrapper for a green node with span information.
|
||||||
///
|
///
|
||||||
/// Owned variant of [`RedRef`]. Can be [cast](Self::cast) to an AST nodes.
|
/// Owned variant of [`RedRef`]. Can be [cast](Self::cast) to an AST node.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct RedNode {
|
pub struct RedNode {
|
||||||
id: SourceId,
|
id: SourceId,
|
||||||
@ -192,15 +192,6 @@ impl RedNode {
|
|||||||
Self { id, offset: 0, green: root.into() }
|
Self { id, offset: 0, green: root.into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new red node from a node kind and a span.
|
|
||||||
pub fn from_data(kind: NodeKind, span: Span) -> Self {
|
|
||||||
Self {
|
|
||||||
id: span.source,
|
|
||||||
offset: span.start,
|
|
||||||
green: Green::Token(GreenData { kind, len: span.len() }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert to a borrowed representation.
|
/// Convert to a borrowed representation.
|
||||||
pub fn as_ref(&self) -> RedRef<'_> {
|
pub fn as_ref(&self) -> RedRef<'_> {
|
||||||
RedRef {
|
RedRef {
|
||||||
@ -527,13 +518,11 @@ pub enum NodeKind {
|
|||||||
EnumNumbering(Option<usize>),
|
EnumNumbering(Option<usize>),
|
||||||
/// An item in an unordered list: `- ...`.
|
/// An item in an unordered list: `- ...`.
|
||||||
List,
|
List,
|
||||||
/// The bullet character of an item in an unordered list: `-`.
|
|
||||||
ListBullet,
|
|
||||||
/// An arbitrary number of backticks followed by inner contents, terminated
|
/// An arbitrary number of backticks followed by inner contents, terminated
|
||||||
/// with the same number of backticks: `` `...` ``.
|
/// with the same number of backticks: `` `...` ``.
|
||||||
Raw(Rc<RawData>),
|
Raw(Rc<RawNode>),
|
||||||
/// Dollar signs surrounding inner contents.
|
/// Dollar signs surrounding inner contents.
|
||||||
Math(Rc<MathData>),
|
Math(Rc<MathNode>),
|
||||||
/// An identifier: `center`.
|
/// An identifier: `center`.
|
||||||
Ident(EcoString),
|
Ident(EcoString),
|
||||||
/// A boolean: `true`, `false`.
|
/// A boolean: `true`, `false`.
|
||||||
@ -613,29 +602,6 @@ pub enum NodeKind {
|
|||||||
Unknown(EcoString),
|
Unknown(EcoString),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Payload of a raw block node.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct RawData {
|
|
||||||
/// The raw text in the block.
|
|
||||||
pub text: EcoString,
|
|
||||||
/// The programming language of the raw text.
|
|
||||||
pub lang: Option<EcoString>,
|
|
||||||
/// The number of opening backticks.
|
|
||||||
pub backticks: u8,
|
|
||||||
/// Whether to display this as a block.
|
|
||||||
pub block: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Payload of a math formula node.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct MathData {
|
|
||||||
/// The formula between the dollars / brackets.
|
|
||||||
pub formula: EcoString,
|
|
||||||
/// Whether the formula is display-level, that is, it is surrounded by
|
|
||||||
/// `$[..]$`.
|
|
||||||
pub display: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where in a node an error should be annotated.
|
/// Where in a node an error should be annotated.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum ErrorPos {
|
pub enum ErrorPos {
|
||||||
@ -730,7 +696,6 @@ impl NodeKind {
|
|||||||
Self::Enum => "enumeration item",
|
Self::Enum => "enumeration item",
|
||||||
Self::EnumNumbering(_) => "enumeration item numbering",
|
Self::EnumNumbering(_) => "enumeration item numbering",
|
||||||
Self::List => "list item",
|
Self::List => "list item",
|
||||||
Self::ListBullet => "list bullet",
|
|
||||||
Self::Raw(_) => "raw block",
|
Self::Raw(_) => "raw block",
|
||||||
Self::Math(_) => "math formula",
|
Self::Math(_) => "math formula",
|
||||||
Self::Ident(_) => "identifier",
|
Self::Ident(_) => "identifier",
|
||||||
|
@ -63,7 +63,6 @@ impl Printer {
|
|||||||
write_item(item, self);
|
write_item(item, self);
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +98,7 @@ impl Pretty for MarkupNode {
|
|||||||
Self::Emph => p.push('_'),
|
Self::Emph => p.push('_'),
|
||||||
Self::Text(text) => p.push_str(text),
|
Self::Text(text) => p.push_str(text),
|
||||||
Self::Raw(raw) => raw.pretty(p),
|
Self::Raw(raw) => raw.pretty(p),
|
||||||
|
Self::Math(math) => math.pretty(p),
|
||||||
Self::Heading(heading) => heading.pretty(p),
|
Self::Heading(heading) => heading.pretty(p),
|
||||||
Self::List(list) => list.pretty(p),
|
Self::List(list) => list.pretty(p),
|
||||||
Self::Enum(enum_) => enum_.pretty(p),
|
Self::Enum(enum_) => enum_.pretty(p),
|
||||||
@ -168,6 +168,20 @@ impl Pretty for RawNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pretty for MathNode {
|
||||||
|
fn pretty(&self, p: &mut Printer) {
|
||||||
|
p.push('$');
|
||||||
|
if self.display {
|
||||||
|
p.push('[');
|
||||||
|
}
|
||||||
|
p.push_str(&self.formula);
|
||||||
|
if self.display {
|
||||||
|
p.push(']');
|
||||||
|
}
|
||||||
|
p.push('$');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Pretty for HeadingNode {
|
impl Pretty for HeadingNode {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
for _ in 0 .. self.level() {
|
for _ in 0 .. self.level() {
|
||||||
@ -253,12 +267,9 @@ impl Pretty for ArrayExpr {
|
|||||||
impl Pretty for DictExpr {
|
impl Pretty for DictExpr {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
p.push('(');
|
p.push('(');
|
||||||
|
let len = p.join(self.items(), ", ", |named, p| named.pretty(p));
|
||||||
let mut items = self.items().peekable();
|
if len == 0 {
|
||||||
if items.peek().is_none() {
|
|
||||||
p.push(':');
|
p.push(':');
|
||||||
} else {
|
|
||||||
p.join(items, ", ", |named, p| named.pretty(p));
|
|
||||||
}
|
}
|
||||||
p.push(')');
|
p.push(')');
|
||||||
}
|
}
|
||||||
@ -291,13 +302,11 @@ impl Pretty for GroupExpr {
|
|||||||
impl Pretty for BlockExpr {
|
impl Pretty for BlockExpr {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
p.push('{');
|
p.push('{');
|
||||||
|
if self.exprs().count() > 1 {
|
||||||
let exprs: Vec<_> = self.exprs().collect();
|
|
||||||
if exprs.len() > 1 {
|
|
||||||
p.push(' ');
|
p.push(' ');
|
||||||
}
|
}
|
||||||
p.join(&exprs, "; ", |expr, p| expr.pretty(p));
|
let len = p.join(self.exprs(), "; ", |expr, p| expr.pretty(p));
|
||||||
if exprs.len() > 1 {
|
if len > 1 {
|
||||||
p.push(' ');
|
p.push(' ');
|
||||||
}
|
}
|
||||||
p.push('}');
|
p.push('}');
|
||||||
@ -348,17 +357,17 @@ impl Pretty for CallExpr {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let args: Vec<_> = self.args().items().collect();
|
let args: Vec<_> = self.args().items().collect();
|
||||||
|
match args.as_slice() {
|
||||||
if let Some(Expr::Template(template)) = args
|
// This can be moved behind the arguments.
|
||||||
.last()
|
//
|
||||||
.and_then(|x| if let CallArg::Pos(arg) = x { Some(arg) } else { None })
|
// Example: Transforms "#v(a, [b])" => "#v(a)[b]".
|
||||||
{
|
[head @ .., CallArg::Pos(Expr::Template(template))] => {
|
||||||
if args.len() > 1 {
|
if !head.is_empty() {
|
||||||
write_args(&args[0 .. args.len() - 1]);
|
write_args(head);
|
||||||
}
|
}
|
||||||
template.pretty(p);
|
template.pretty(p);
|
||||||
} else {
|
}
|
||||||
write_args(&args);
|
items => write_args(items),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -423,12 +432,12 @@ impl Pretty for LetExpr {
|
|||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
p.push_str("let ");
|
p.push_str("let ");
|
||||||
self.binding().pretty(p);
|
self.binding().pretty(p);
|
||||||
if let Some(Expr::Closure(closure)) = &self.init() {
|
if let Some(Expr::Closure(closure)) = self.init() {
|
||||||
p.push('(');
|
p.push('(');
|
||||||
p.join(closure.params(), ", ", |item, p| item.pretty(p));
|
p.join(closure.params(), ", ", |item, p| item.pretty(p));
|
||||||
p.push_str(") = ");
|
p.push_str(") = ");
|
||||||
closure.body().pretty(p);
|
closure.body().pretty(p);
|
||||||
} else if let Some(init) = &self.init() {
|
} else if let Some(init) = self.init() {
|
||||||
p.push_str(" = ");
|
p.push_str(" = ");
|
||||||
init.pretty(p);
|
init.pretty(p);
|
||||||
}
|
}
|
||||||
@ -441,7 +450,7 @@ impl Pretty for IfExpr {
|
|||||||
self.condition().pretty(p);
|
self.condition().pretty(p);
|
||||||
p.push(' ');
|
p.push(' ');
|
||||||
self.if_body().pretty(p);
|
self.if_body().pretty(p);
|
||||||
if let Some(expr) = &self.else_body() {
|
if let Some(expr) = self.else_body() {
|
||||||
p.push_str(" else ");
|
p.push_str(" else ");
|
||||||
expr.pretty(p);
|
expr.pretty(p);
|
||||||
}
|
}
|
||||||
@ -525,7 +534,7 @@ mod tests {
|
|||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_parse(src: &str, expected: &str) {
|
fn test_parse(src: &str, expected: &str) {
|
||||||
let source = SourceFile::detached(src);
|
let source = SourceFile::detached(src);
|
||||||
let ast: Markup = source.ast().unwrap();
|
let ast = source.ast().unwrap();
|
||||||
let found = pretty(&ast);
|
let found = pretty(&ast);
|
||||||
if found != expected {
|
if found != expected {
|
||||||
println!("tree: {:#?}", ast);
|
println!("tree: {:#?}", ast);
|
||||||
@ -563,6 +572,11 @@ mod tests {
|
|||||||
test_parse("``` 1```", "`1`");
|
test_parse("``` 1```", "`1`");
|
||||||
test_parse("``` 1 ```", "`1 `");
|
test_parse("``` 1 ```", "`1 `");
|
||||||
test_parse("```` ` ````", "``` ` ```");
|
test_parse("```` ` ````", "``` ` ```");
|
||||||
|
|
||||||
|
// Math node.
|
||||||
|
roundtrip("$$");
|
||||||
|
roundtrip("$a+b$");
|
||||||
|
roundtrip("$[ a^2 + b^2 = c^2 ]$");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
BIN
tests/ref/markup/math.png
Normal file
BIN
tests/ref/markup/math.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
@ -72,7 +72,7 @@
|
|||||||
{(,1)}
|
{(,1)}
|
||||||
|
|
||||||
// Missing expression makes named pair incomplete, making this an empty array.
|
// Missing expression makes named pair incomplete, making this an empty array.
|
||||||
// Error: 3-5 expected expression, found named pair
|
// Error: 5 expected expression
|
||||||
{(a:)}
|
{(a:)}
|
||||||
|
|
||||||
// Named pair after this is already identified as an array.
|
// Named pair after this is already identified as an array.
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
// Error: 10-12 expected expression, found end of block comment
|
// Error: 10-12 expected expression, found end of block comment
|
||||||
#func(a:1*/)
|
#func(a:1*/)
|
||||||
|
|
||||||
// Error: 9 expected comma
|
// Error: 8 expected comma
|
||||||
#func(1 2)
|
#func(1 2)
|
||||||
|
|
||||||
// Error: 7-8 expected identifier
|
// Error: 7-8 expected identifier
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
// Identified as dictionary due to initial colon.
|
// Identified as dictionary due to initial colon.
|
||||||
// Error: 4-5 expected named pair, found expression
|
// Error: 4-5 expected named pair, found expression
|
||||||
// Error: 6 expected comma
|
// Error: 5 expected comma
|
||||||
// Error: 12-16 expected identifier
|
// Error: 12-16 expected identifier
|
||||||
// Error: 17-18 expected expression, found colon
|
// Error: 17-18 expected expression, found colon
|
||||||
{(:1 b:"", true::)}
|
{(:1 b:"", true::)}
|
||||||
|
12
tests/typ/markup/math.typ
Normal file
12
tests/typ/markup/math.typ
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Test math formulas.
|
||||||
|
|
||||||
|
---
|
||||||
|
The sum of $a$ and $b$ is $a + b$.
|
||||||
|
|
||||||
|
---
|
||||||
|
We will show that:
|
||||||
|
$[ a^2 + b^2 = c^2 ]$
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 2:1 expected closing bracket and dollar sign
|
||||||
|
$[a
|
Loading…
x
Reference in New Issue
Block a user