Move all pretty printing into one module and pretty print values 🦋

This commit is contained in:
Laurenz 2021-02-11 17:33:13 +01:00
parent f9197dcfef
commit 1711b67877
16 changed files with 678 additions and 683 deletions

View File

@ -2,7 +2,7 @@
//!
//! Errors are never fatal, the document will always compile and yield a layout.
use crate::syntax::SpanVec;
use crate::syntax::Spanned;
use std::fmt::{self, Display, Formatter};
/// The result of some pass: Some output `T` and [`Feedback`] data.
@ -25,9 +25,9 @@ impl<T> Pass<T> {
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Feedback {
/// Diagnostics about the source code.
pub diags: SpanVec<Diag>,
pub diags: Vec<Spanned<Diag>>,
/// Decorations of the source code for semantic syntax highlighting.
pub decos: SpanVec<Deco>,
pub decos: Vec<Spanned<Deco>>,
}
impl Feedback {

View File

@ -7,6 +7,7 @@ use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use fontdock::fs::FsSource;
use image::io::Reader as ImageReader;
use image::{DynamicImage, GenericImageView, ImageFormat};
@ -21,6 +22,16 @@ pub struct Env {
pub resources: ResourceLoader,
}
impl Env {
/// Create an empty environment for testing purposes.
pub fn blank() -> Self {
Self {
fonts: FontLoader::new(Box::new(FsSource::new(vec![])), vec![]),
resources: ResourceLoader::new(),
}
}
}
/// Loads resource from the file system.
pub struct ResourceLoader {
paths: HashMap<PathBuf, ResourceId>,

View File

@ -377,11 +377,11 @@ impl Eval for ExprArgs {
.map(|arg| match arg {
ExprArg::Pos(expr) => ValueArg {
name: None,
value: expr.eval(ctx).with_span(expr.span()),
value: Spanned::new(expr.eval(ctx), expr.span()),
},
ExprArg::Named(Named { name, expr }) => ValueArg {
name: Some(name.string.clone().with_span(name.span)),
value: expr.eval(ctx).with_span(expr.span()),
name: Some(Spanned::new(name.string.clone(), name.span)),
value: Spanned::new(expr.eval(ctx), expr.span()),
},
})
.collect();

View File

@ -97,44 +97,9 @@ impl Default for Value {
}
}
impl Pretty for Value {
fn pretty(&self, p: &mut Printer) {
match self {
Value::None => p.push_str("none"),
Value::Bool(v) => v.pretty(p),
Value::Int(v) => v.pretty(p),
Value::Float(v) => v.pretty(p),
Value::Length(v) => v.pretty(p),
Value::Angle(v) => v.pretty(p),
Value::Relative(v) => v.pretty(p),
Value::Linear(v) => v.pretty(p),
Value::Color(v) => v.pretty(p),
Value::Str(v) => v.pretty(p),
Value::Array(v) => v.pretty(p),
Value::Dict(v) => v.pretty(p),
Value::Template(v) => v.pretty(p),
Value::Func(v) => v.pretty(p),
Value::Args(v) => v.pretty(p),
Value::Any(v) => v.pretty(p),
Value::Error => p.push_str("<error>"),
}
}
}
/// An array value: `(1, "hi", 12cm)`.
pub type ValueArray = Vec<Value>;
impl Pretty for ValueArray {
fn pretty(&self, p: &mut Printer) {
p.push('(');
p.join(self, ", ", |item, p| item.pretty(p));
if self.len() == 1 {
p.push(',');
}
p.push(')');
}
}
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
pub type ValueDict = BTreeMap<String, Value>;
@ -157,16 +122,6 @@ impl Pretty for ValueDict {
/// A template value: `[*Hi* there]`.
pub type ValueTemplate = Vec<TemplateNode>;
impl Pretty for ValueTemplate {
fn pretty(&self, p: &mut Printer) {
p.push('[');
for part in self {
part.pretty(p);
}
p.push(']');
}
}
/// One chunk of a template.
///
/// Evaluating a template expression creates only a single chunk. Adding two
@ -185,16 +140,6 @@ pub enum TemplateNode {
Any(TemplateAny),
}
impl Pretty for TemplateNode {
fn pretty(&self, p: &mut Printer) {
match self {
// TODO: Pretty-print the values.
Self::Tree { tree, .. } => tree.pretty(p),
Self::Any(any) => any.pretty(p),
}
}
}
/// A reference-counted dynamic template node (can implement custom behaviour).
#[derive(Clone)]
pub struct TemplateAny {
@ -210,6 +155,12 @@ impl TemplateAny {
{
Self { name: name.into(), f: Rc::new(f) }
}
/// The name of the template node.
pub fn name(&self) -> &str {
&self.name
}
}
impl PartialEq for TemplateAny {
@ -227,14 +178,6 @@ impl Deref for TemplateAny {
}
}
impl Pretty for TemplateAny {
fn pretty(&self, p: &mut Printer) {
p.push('<');
p.push_str(&self.name);
p.push('>');
}
}
impl Debug for TemplateAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("TemplateAny").finish()
@ -256,6 +199,11 @@ impl ValueFunc {
{
Self { name: name.into(), f: Rc::new(f) }
}
/// The name of the function.
pub fn name(&self) -> &str {
&self.name
}
}
impl PartialEq for ValueFunc {
@ -273,14 +221,6 @@ impl Deref for ValueFunc {
}
}
impl Pretty for ValueFunc {
fn pretty(&self, p: &mut Printer) {
p.push('<');
p.push_str(&self.name);
p.push('>');
}
}
impl Debug for ValueFunc {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("ValueFunc").field("name", &self.name).finish()
@ -417,14 +357,6 @@ impl ValueArgs {
}
}
impl Pretty for ValueArgs {
fn pretty(&self, p: &mut Printer) {
p.push('<');
p.join(&self.items, ", ", |item, p| item.pretty(p));
p.push('>');
}
}
// This is a workaround because `-> impl Trait + 'a + 'b` does not work.
//
// See also: https://github.com/rust-lang/rust/issues/49431
@ -451,16 +383,6 @@ impl ValueArg {
}
}
impl Pretty for ValueArg {
fn pretty(&self, p: &mut Printer) {
if let Some(name) = &self.name {
p.push_str(&name.v);
p.push_str(": ");
}
self.value.v.pretty(p);
}
}
/// A wrapper around a dynamic value.
pub struct ValueAny(Box<dyn Bounds>);
@ -510,18 +432,18 @@ impl PartialEq for ValueAny {
}
}
impl Pretty for ValueAny {
fn pretty(&self, p: &mut Printer) {
write!(p, "{}", self.0).unwrap();
}
}
impl Debug for ValueAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_tuple("ValueAny").field(&self.0).finish()
}
}
impl Display for ValueAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
trait Bounds: Debug + Display + 'static {
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
@ -618,7 +540,7 @@ where
match T::cast(value.v) {
CastResult::Ok(t) => CastResult::Ok(t),
CastResult::Warn(t, m) => CastResult::Warn(t, m),
CastResult::Err(v) => CastResult::Err(v.with_span(span)),
CastResult::Err(v) => CastResult::Err(Spanned::new(v, span)),
}
}
}
@ -630,9 +552,9 @@ where
fn cast(value: Spanned<Value>) -> CastResult<Self, Spanned<Value>> {
let span = value.span;
match T::cast(value.v) {
CastResult::Ok(t) => CastResult::Ok(t.with_span(span)),
CastResult::Warn(t, m) => CastResult::Warn(t.with_span(span), m),
CastResult::Err(v) => CastResult::Err(v.with_span(span)),
CastResult::Ok(t) => CastResult::Ok(Spanned::new(t, span)),
CastResult::Warn(t, m) => CastResult::Warn(Spanned::new(t, span), m),
CastResult::Err(v) => CastResult::Err(Spanned::new(v, span)),
}
}
}

View File

@ -33,7 +33,7 @@ pub fn exec(
) -> Pass<layout::Tree> {
let mut ctx = ExecContext::new(env, state);
ctx.start_page_group(Softness::Hard);
tree.exec_with(&mut ctx, &map);
tree.exec_with_map(&mut ctx, &map);
ctx.end_page_group(|s| s == Softness::Hard);
ctx.finish()
}
@ -51,21 +51,21 @@ pub trait Exec {
}
/// Execute a node with an expression map that applies to it.
pub trait ExecWith {
pub trait ExecWithMap {
/// Execute the node.
fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap);
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap);
}
impl ExecWith for Tree {
fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) {
impl ExecWithMap for Tree {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
for node in self {
node.exec_with(ctx, map);
node.exec_with_map(ctx, map);
}
}
}
impl ExecWith for Node {
fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) {
impl ExecWithMap for Node {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
match self {
Node::Text(text) => ctx.push_text(text),
Node::Space => ctx.push_space(),
@ -73,21 +73,21 @@ impl ExecWith for Node {
Node::Parbreak => ctx.apply_parbreak(),
Node::Strong => ctx.state.font.strong ^= true,
Node::Emph => ctx.state.font.emph ^= true,
Node::Heading(heading) => heading.exec_with(ctx, map),
Node::Heading(heading) => heading.exec_with_map(ctx, map),
Node::Raw(raw) => raw.exec(ctx),
Node::Expr(expr) => map[&(expr as *const _)].exec(ctx),
}
}
}
impl ExecWith for NodeHeading {
fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) {
impl ExecWithMap for NodeHeading {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
let prev = ctx.state.clone();
let upscale = 1.5 - 0.1 * self.level as f64;
ctx.state.font.scale *= upscale;
ctx.state.font.strong = true;
self.contents.exec_with(ctx, map);
self.contents.exec_with_map(ctx, map);
ctx.apply_parbreak();
ctx.state = prev;
@ -154,7 +154,7 @@ impl Exec for ValueTemplate {
impl Exec for TemplateNode {
fn exec(&self, ctx: &mut ExecContext) {
match self {
Self::Tree { tree, map } => tree.exec_with(ctx, &map),
Self::Tree { tree, map } => tree.exec_with_map(ctx, &map),
Self::Any(any) => any.exec(ctx),
}
}

View File

@ -1,5 +1,5 @@
use super::Scanner;
use crate::syntax::{Location, Offset, Pos};
use crate::syntax::{Location, Pos};
/// Enables conversion of byte position to locations.
pub struct LineMap<'s> {
@ -44,7 +44,7 @@ impl<'s> LineMap<'s> {
pub fn pos(&self, location: Location) -> Option<Pos> {
// Determine the boundaries of the line.
let line_idx = location.line.checked_sub(1)? as usize;
let line_start = self.line_starts.get(line_idx)?;
let line_start = *self.line_starts.get(line_idx)?;
let line_end = self
.line_starts
.get(location.line as usize)
@ -64,7 +64,7 @@ impl<'s> LineMap<'s> {
0
};
Some(line_start.offset(Pos(line_offset as u32)))
Some(line_start + line_offset)
}
}

View File

@ -1,5 +1,5 @@
use super::{is_newline, Scanner};
use crate::syntax::{Ident, NodeRaw, Offset, Pos};
use crate::syntax::{Ident, NodeRaw, Pos};
/// Resolve all escape sequences in a string.
pub fn resolve_string(string: &str) -> String {
@ -52,7 +52,7 @@ pub fn resolve_raw(text: &str, backticks: usize, start: Pos) -> NodeRaw {
let (tag, inner) = split_at_lang_tag(text);
let (lines, had_newline) = trim_and_split_raw(inner);
NodeRaw {
lang: Ident::new(tag, start .. start.offset(tag.len())),
lang: Ident::new(tag, start .. start + tag.len()),
lines,
block: had_newline,
}

View File

@ -12,5 +12,5 @@ pub use crate::geom::*;
#[doc(no_inline)]
pub use crate::layout::Node;
#[doc(no_inline)]
pub use crate::syntax::{Span, Spanned, WithSpan};
pub use crate::syntax::{Span, Spanned};
pub use crate::{error, typify, warning};

View File

@ -3,7 +3,9 @@
use std::fmt::{Arguments, Result, Write};
use crate::color::{Color, RgbaColor};
use crate::eval::*;
use crate::geom::{Angle, Length, Linear, Relative};
use crate::syntax::*;
/// Pretty print an item and return the resulting string.
pub fn pretty<T>(item: &T) -> String
@ -15,12 +17,37 @@ where
p.finish()
}
/// Pretty printing.
/// Pretty print an item with an expression map and return the resulting string.
pub fn pretty_with_map<T>(item: &T, map: &ExprMap) -> String
where
T: PrettyWithMap + ?Sized,
{
let mut p = Printer::new();
item.pretty_with_map(&mut p, Some(map));
p.finish()
}
/// Pretty print an item.
pub trait Pretty {
/// Pretty print this item into the given printer.
fn pretty(&self, p: &mut Printer);
}
/// Pretty print an item with an expression map that applies to it.
pub trait PrettyWithMap {
/// Pretty print this item into the given printer.
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>);
}
impl<T> Pretty for T
where
T: PrettyWithMap,
{
fn pretty(&self, p: &mut Printer) {
self.pretty_with_map(p, None);
}
}
/// A buffer into which items are printed.
pub struct Printer {
buf: String,
@ -76,6 +103,463 @@ impl Write for Printer {
}
}
impl PrettyWithMap for Tree {
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) {
for node in self {
node.pretty_with_map(p, map);
}
}
}
impl PrettyWithMap for Node {
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) {
match self {
Self::Strong => p.push('*'),
Self::Emph => p.push('_'),
Self::Space => p.push(' '),
Self::Linebreak => p.push_str(r"\"),
Self::Parbreak => p.push_str("\n\n"),
// TODO: Handle escaping.
Self::Text(text) => p.push_str(&text),
Self::Heading(heading) => heading.pretty_with_map(p, map),
Self::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => {
if let Some(map) = map {
let value = &map[&(expr as *const _)];
value.pretty(p);
} else if let Expr::Call(call) = expr {
// Format function templates appropriately.
pretty_bracketed(call, p, false)
} else {
expr.pretty(p);
}
}
}
}
}
impl PrettyWithMap for NodeHeading {
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) {
for _ in 0 ..= self.level {
p.push('=');
}
self.contents.pretty_with_map(p, map);
}
}
impl Pretty for NodeRaw {
fn pretty(&self, p: &mut Printer) {
// Find out how many backticks we need.
let mut backticks = 1;
// Language tag and block-level are only possible with 3+ backticks.
if self.lang.is_some() || self.block {
backticks = 3;
}
// More backticks may be required if there are lots of consecutive
// backticks in the lines.
let mut count;
for line in &self.lines {
count = 0;
for c in line.chars() {
if c == '`' {
count += 1;
backticks = backticks.max(3).max(count + 1);
} else {
count = 0;
}
}
}
// Starting backticks.
for _ in 0 .. backticks {
p.push('`');
}
// Language tag.
if let Some(lang) = &self.lang {
lang.pretty(p);
}
// Start untrimming.
if self.block {
p.push('\n');
} else if backticks >= 3 {
p.push(' ');
}
// The lines.
p.join(&self.lines, "\n", |line, p| p.push_str(line));
// End untrimming.
if self.block {
p.push('\n');
} else if self.lines.last().map_or(false, |line| line.trim_end().ends_with('`')) {
p.push(' ');
}
// Ending backticks.
for _ in 0 .. backticks {
p.push('`');
}
}
}
impl Pretty for Expr {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Lit(v) => v.pretty(p),
Self::Ident(v) => v.pretty(p),
Self::Array(v) => v.pretty(p),
Self::Dict(v) => v.pretty(p),
Self::Template(v) => v.pretty(p),
Self::Group(v) => v.pretty(p),
Self::Block(v) => v.pretty(p),
Self::Unary(v) => v.pretty(p),
Self::Binary(v) => v.pretty(p),
Self::Call(v) => v.pretty(p),
Self::Let(v) => v.pretty(p),
Self::If(v) => v.pretty(p),
Self::For(v) => v.pretty(p),
}
}
}
impl Pretty for Lit {
fn pretty(&self, p: &mut Printer) {
self.kind.pretty(p);
}
}
impl Pretty for LitKind {
fn pretty(&self, p: &mut Printer) {
match self {
Self::None => p.push_str("none"),
Self::Bool(v) => v.pretty(p),
Self::Int(v) => v.pretty(p),
Self::Float(v) => v.pretty(p),
Self::Length(v, u) => {
write!(p, "{}{}", ryu::Buffer::new().format(*v), u).unwrap();
}
Self::Angle(v, u) => {
write!(p, "{}{}", ryu::Buffer::new().format(*v), u).unwrap();
}
Self::Percent(v) => {
write!(p, "{}%", ryu::Buffer::new().format(*v)).unwrap();
}
Self::Color(v) => v.pretty(p),
Self::Str(v) => v.pretty(p),
}
}
}
impl Pretty for ExprArray {
fn pretty(&self, p: &mut Printer) {
p.push('(');
p.join(&self.items, ", ", |item, p| item.pretty(p));
if self.items.len() == 1 {
p.push(',');
}
p.push(')');
}
}
impl Pretty for ExprDict {
fn pretty(&self, p: &mut Printer) {
p.push('(');
if self.items.is_empty() {
p.push(':');
} else {
p.join(&self.items, ", ", |named, p| named.pretty(p));
}
p.push(')');
}
}
impl Pretty for Named {
fn pretty(&self, p: &mut Printer) {
self.name.pretty(p);
p.push_str(": ");
self.expr.pretty(p);
}
}
impl Pretty for ExprTemplate {
fn pretty(&self, p: &mut Printer) {
if let [Node::Expr(Expr::Call(call))] = self.tree.as_slice() {
pretty_bracketed(call, p, false);
} else {
p.push('[');
self.tree.pretty_with_map(p, None);
p.push(']');
}
}
}
impl Pretty for ExprGroup {
fn pretty(&self, p: &mut Printer) {
p.push('(');
self.expr.pretty(p);
p.push(')');
}
}
impl Pretty for ExprBlock {
fn pretty(&self, p: &mut Printer) {
p.push('{');
if self.exprs.len() > 1 {
p.push(' ');
}
p.join(&self.exprs, "; ", |expr, p| expr.pretty(p));
if self.exprs.len() > 1 {
p.push(' ');
}
p.push('}');
}
}
impl Pretty for ExprUnary {
fn pretty(&self, p: &mut Printer) {
self.op.pretty(p);
if self.op == UnOp::Not {
p.push(' ');
}
self.expr.pretty(p);
}
}
impl Pretty for UnOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(self.as_str());
}
}
impl Pretty for ExprBinary {
fn pretty(&self, p: &mut Printer) {
self.lhs.pretty(p);
p.push(' ');
self.op.pretty(p);
p.push(' ');
self.rhs.pretty(p);
}
}
impl Pretty for BinOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(self.as_str());
}
}
impl Pretty for ExprCall {
fn pretty(&self, p: &mut Printer) {
self.callee.pretty(p);
p.push('(');
self.args.pretty(p);
p.push(')');
}
}
/// Pretty print a function template, with body or chaining when possible.
pub fn pretty_bracketed(call: &ExprCall, p: &mut Printer, chained: bool) {
if chained {
p.push_str(" | ");
} else {
p.push_str("#[");
}
// Function name.
call.callee.pretty(p);
let mut write_args = |items: &[ExprArg]| {
if !items.is_empty() {
p.push(' ');
p.join(items, ", ", |item, p| item.pretty(p));
}
};
match call.args.items.as_slice() {
// This can written as a chain.
//
// Example: Transforms "#[v][[f]]" => "#[v | f]".
[head @ .., ExprArg::Pos(Expr::Call(call))] => {
write_args(head);
pretty_bracketed(call, p, true);
}
// This can be written with a body.
//
// Example: Transforms "#[v [Hi]]" => "#[v][Hi]".
[head @ .., ExprArg::Pos(Expr::Template(template))] => {
write_args(head);
p.push(']');
template.pretty(p);
}
items => {
write_args(items);
p.push(']');
}
}
}
impl Pretty for ExprArgs {
fn pretty(&self, p: &mut Printer) {
p.join(&self.items, ", ", |item, p| item.pretty(p));
}
}
impl Pretty for ExprArg {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Pos(expr) => expr.pretty(p),
Self::Named(named) => named.pretty(p),
}
}
}
impl Pretty for ExprLet {
fn pretty(&self, p: &mut Printer) {
p.push_str("#let ");
self.binding.pretty(p);
if let Some(init) = &self.init {
p.push_str(" = ");
init.pretty(p);
}
}
}
impl Pretty for ExprIf {
fn pretty(&self, p: &mut Printer) {
p.push_str("#if ");
self.condition.pretty(p);
p.push(' ');
self.if_body.pretty(p);
if let Some(expr) = &self.else_body {
p.push_str(" #else ");
expr.pretty(p);
}
}
}
impl Pretty for ExprFor {
fn pretty(&self, p: &mut Printer) {
p.push_str("#for ");
self.pattern.pretty(p);
p.push_str(" #in ");
self.iter.pretty(p);
p.push(' ');
self.body.pretty(p);
}
}
impl Pretty for ForPattern {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Value(v) => v.pretty(p),
Self::KeyValue(k, v) => {
k.pretty(p);
p.push_str(", ");
v.pretty(p);
}
}
}
}
impl Pretty for Ident {
fn pretty(&self, p: &mut Printer) {
p.push_str(self.as_str());
}
}
impl Pretty for Value {
fn pretty(&self, p: &mut Printer) {
match self {
Value::None => p.push_str("none"),
Value::Bool(v) => v.pretty(p),
Value::Int(v) => v.pretty(p),
Value::Float(v) => v.pretty(p),
Value::Length(v) => v.pretty(p),
Value::Angle(v) => v.pretty(p),
Value::Relative(v) => v.pretty(p),
Value::Linear(v) => v.pretty(p),
Value::Color(v) => v.pretty(p),
Value::Str(v) => v.pretty(p),
Value::Array(v) => v.pretty(p),
Value::Dict(v) => v.pretty(p),
Value::Template(v) => v.pretty(p),
Value::Func(v) => v.pretty(p),
Value::Args(v) => v.pretty(p),
Value::Any(v) => v.pretty(p),
Value::Error => p.push_str("<error>"),
}
}
}
impl Pretty for ValueArray {
fn pretty(&self, p: &mut Printer) {
p.push('(');
p.join(self, ", ", |item, p| item.pretty(p));
if self.len() == 1 {
p.push(',');
}
p.push(')');
}
}
impl Pretty for ValueTemplate {
fn pretty(&self, p: &mut Printer) {
p.push('[');
for part in self {
part.pretty(p);
}
p.push(']');
}
}
impl Pretty for TemplateNode {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Tree { tree, map } => tree.pretty_with_map(p, Some(map)),
Self::Any(any) => any.pretty(p),
}
}
}
impl Pretty for TemplateAny {
fn pretty(&self, p: &mut Printer) {
p.push('<');
p.push_str(self.name());
p.push('>');
}
}
impl Pretty for ValueFunc {
fn pretty(&self, p: &mut Printer) {
p.push('<');
p.push_str(self.name());
p.push('>');
}
}
impl Pretty for ValueArgs {
fn pretty(&self, p: &mut Printer) {
p.push('<');
p.join(&self.items, ", ", |item, p| item.pretty(p));
p.push('>');
}
}
impl Pretty for ValueArg {
fn pretty(&self, p: &mut Printer) {
if let Some(name) = &self.name {
p.push_str(&name.v);
p.push_str(": ");
}
self.value.v.pretty(p);
}
}
impl Pretty for i64 {
fn pretty(&self, p: &mut Printer) {
p.push_str(itoa::Buffer::new().format(*self));
@ -123,11 +607,134 @@ pretty_display! {
Linear,
RgbaColor,
Color,
ValueAny,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::env::Env;
use crate::eval::eval;
use crate::parse::parse;
#[track_caller]
fn test(src: &str, exp: &str) {
let tree = parse(src).output;
let found = pretty(&tree);
if exp != found {
println!("tree: {:#?}", tree);
println!("expected: {}", exp);
println!("found: {}", found);
panic!("test failed");
}
}
#[track_caller]
fn roundtrip(src: &str) {
test(src, src);
}
#[test]
fn test_pretty_print_node() {
// Basic text and markup.
roundtrip("*");
roundtrip("_");
roundtrip(" ");
roundtrip("\\ ");
roundtrip("\n\n");
roundtrip("hi");
// Heading.
roundtrip("= *Ok*");
// Raw.
roundtrip("``");
roundtrip("`nolang 1`");
roundtrip("```lang 1```");
roundtrip("```lang 1 ```");
roundtrip("```hi line ```");
roundtrip("```py\ndef\n```");
roundtrip("```\n line \n```");
roundtrip("```\n`\n```");
roundtrip("``` ` ```");
roundtrip("````\n```\n```\n````");
test("```lang```", "```lang ```");
test("```1 ```", "``");
test("``` 1```", "`1`");
test("``` 1 ```", "`1 `");
test("```` ` ````", "``` ` ```");
}
#[test]
fn test_pretty_print_expr() {
// Basic expressions.
roundtrip("{none}");
roundtrip("{hi}");
roundtrip("{true}");
roundtrip("{10}");
roundtrip("{3.14}");
roundtrip("{10.0pt}");
roundtrip("{14.1deg}");
roundtrip("{20.0%}");
roundtrip("{#abcdef}");
roundtrip(r#"{"hi"}"#);
test(r#"{"let's \" go"}"#, r#"{"let's \" go"}"#);
// Arrays.
roundtrip("{()}");
roundtrip("{(1)}");
roundtrip("{(1, 2, 3)}");
// Dictionaries.
roundtrip("{(:)}");
roundtrip("{(key: value)}");
roundtrip("{(a: 1, b: 2)}");
// Templates.
roundtrip("[]");
roundtrip("[*Ok*]");
roundtrip("{[f]}");
// Groups.
roundtrip("{(1)}");
// Blocks.
roundtrip("{}");
roundtrip("{1}");
roundtrip("{ #let x = 1; x += 2; x + 1 }");
roundtrip("[{}]");
// Operators.
roundtrip("{-x}");
roundtrip("{not true}");
roundtrip("{1 + 3}");
// Parenthesized calls.
roundtrip("{v()}");
roundtrip("{v(1)}");
roundtrip("{v(a: 1, b)}");
// Function templates.
roundtrip("#[v]");
roundtrip("#[v 1]");
roundtrip("#[v 1, 2][*Ok*]");
roundtrip("#[v 1 | f 2]");
test("{#[v]}", "{v()}");
test("#[v 1, #[f 2]]", "#[v 1 | f 2]");
// Keywords.
roundtrip("#let x = 1 + 2");
roundtrip("#if x [y] #else [z]");
roundtrip("#for x #in y {z}");
roundtrip("#for k, x #in y {z}");
}
#[test]
fn test_pretty_print_with_map() {
let tree = parse("*[{1+2}[{4}]]*{2+3}").output;
let map = eval(&mut Env::blank(), &tree, &Default::default()).output;
assert_eq!(pretty_with_map(&tree, &map), "*[3[4]]*5");
}
#[test]
fn test_pretty_print_str() {

View File

@ -56,26 +56,6 @@ impl Expr {
}
}
impl Pretty for Expr {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Lit(v) => v.pretty(p),
Self::Ident(v) => v.pretty(p),
Self::Array(v) => v.pretty(p),
Self::Dict(v) => v.pretty(p),
Self::Template(v) => v.pretty(p),
Self::Group(v) => v.pretty(p),
Self::Block(v) => v.pretty(p),
Self::Unary(v) => v.pretty(p),
Self::Binary(v) => v.pretty(p),
Self::Call(v) => v.pretty(p),
Self::Let(v) => v.pretty(p),
Self::If(v) => v.pretty(p),
Self::For(v) => v.pretty(p),
}
}
}
/// A literal.
#[derive(Debug, Clone, PartialEq)]
pub struct Lit {
@ -85,12 +65,6 @@ pub struct Lit {
pub kind: LitKind,
}
impl Pretty for Lit {
fn pretty(&self, p: &mut Printer) {
self.kind.pretty(p);
}
}
/// A kind of literal.
#[derive(Debug, Clone, PartialEq)]
pub enum LitKind {
@ -117,28 +91,6 @@ pub enum LitKind {
Str(String),
}
impl Pretty for LitKind {
fn pretty(&self, p: &mut Printer) {
match self {
Self::None => p.push_str("none"),
Self::Bool(v) => v.pretty(p),
Self::Int(v) => v.pretty(p),
Self::Float(v) => v.pretty(p),
Self::Length(v, u) => {
write!(p, "{}{}", ryu::Buffer::new().format(*v), u).unwrap();
}
Self::Angle(v, u) => {
write!(p, "{}{}", ryu::Buffer::new().format(*v), u).unwrap();
}
Self::Percent(v) => {
write!(p, "{}%", ryu::Buffer::new().format(*v)).unwrap();
}
Self::Color(v) => v.pretty(p),
Self::Str(v) => v.pretty(p),
}
}
}
/// An array expression: `(1, "hi", 12cm)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprArray {
@ -148,17 +100,6 @@ pub struct ExprArray {
pub items: Vec<Expr>,
}
impl Pretty for ExprArray {
fn pretty(&self, p: &mut Printer) {
p.push('(');
p.join(&self.items, ", ", |item, p| item.pretty(p));
if self.items.len() == 1 {
p.push(',');
}
p.push(')');
}
}
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprDict {
@ -168,18 +109,6 @@ pub struct ExprDict {
pub items: Vec<Named>,
}
impl Pretty for ExprDict {
fn pretty(&self, p: &mut Printer) {
p.push('(');
if self.items.is_empty() {
p.push(':');
} else {
p.join(&self.items, ", ", |named, p| named.pretty(p));
}
p.push(')');
}
}
/// A pair of a name and an expression: `pattern: dashed`.
#[derive(Debug, Clone, PartialEq)]
pub struct Named {
@ -196,14 +125,6 @@ impl Named {
}
}
impl Pretty for Named {
fn pretty(&self, p: &mut Printer) {
self.name.pretty(p);
p.push_str(": ");
self.expr.pretty(p);
}
}
/// A template expression: `[*Hi* there!]`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprTemplate {
@ -213,18 +134,6 @@ pub struct ExprTemplate {
pub tree: Rc<Tree>,
}
impl Pretty for ExprTemplate {
fn pretty(&self, p: &mut Printer) {
if let [Node::Expr(Expr::Call(call))] = self.tree.as_slice() {
call.pretty_bracketed(p, false);
} else {
p.push('[');
self.tree.pretty(p);
p.push(']');
}
}
}
/// A grouped expression: `(1 + 2)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprGroup {
@ -234,14 +143,6 @@ pub struct ExprGroup {
pub expr: Box<Expr>,
}
impl Pretty for ExprGroup {
fn pretty(&self, p: &mut Printer) {
p.push('(');
self.expr.pretty(p);
p.push(')');
}
}
/// A block expression: `{ #let x = 1; x + 2 }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBlock {
@ -253,20 +154,6 @@ pub struct ExprBlock {
pub scoping: bool,
}
impl Pretty for ExprBlock {
fn pretty(&self, p: &mut Printer) {
p.push('{');
if self.exprs.len() > 1 {
p.push(' ');
}
p.join(&self.exprs, "; ", |expr, p| expr.pretty(p));
if self.exprs.len() > 1 {
p.push(' ');
}
p.push('}');
}
}
/// A unary operation: `-x`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprUnary {
@ -278,16 +165,6 @@ pub struct ExprUnary {
pub expr: Box<Expr>,
}
impl Pretty for ExprUnary {
fn pretty(&self, p: &mut Printer) {
self.op.pretty(p);
if self.op == UnOp::Not {
p.push(' ');
}
self.expr.pretty(p);
}
}
/// A unary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum UnOp {
@ -328,12 +205,6 @@ impl UnOp {
}
}
impl Pretty for UnOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(self.as_str());
}
}
/// A binary operation: `a + b`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBinary {
@ -347,16 +218,6 @@ pub struct ExprBinary {
pub rhs: Box<Expr>,
}
impl Pretty for ExprBinary {
fn pretty(&self, p: &mut Printer) {
self.lhs.pretty(p);
p.push(' ');
self.op.pretty(p);
p.push(' ');
self.rhs.pretty(p);
}
}
/// A binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BinOp {
@ -484,12 +345,6 @@ impl BinOp {
}
}
impl Pretty for BinOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(self.as_str());
}
}
/// The associativity of a binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Associativity {
@ -510,60 +365,6 @@ pub struct ExprCall {
pub args: ExprArgs,
}
impl Pretty for ExprCall {
fn pretty(&self, p: &mut Printer) {
self.callee.pretty(p);
p.push('(');
self.args.pretty(p);
p.push(')');
}
}
impl ExprCall {
/// Pretty print a function template, with body or chaining when possible.
pub fn pretty_bracketed(&self, p: &mut Printer, chained: bool) {
if chained {
p.push_str(" | ");
} else {
p.push_str("#[");
}
// Function name.
self.callee.pretty(p);
let mut write_args = |items: &[ExprArg]| {
if !items.is_empty() {
p.push(' ');
p.join(items, ", ", |item, p| item.pretty(p));
}
};
match self.args.items.as_slice() {
// This can written as a chain.
//
// Example: Transforms "#[v][[f]]" => "#[v | f]".
[head @ .., ExprArg::Pos(Expr::Call(call))] => {
write_args(head);
call.pretty_bracketed(p, true);
}
// This can be written with a body.
//
// Example: Transforms "#[v [Hi]]" => "#[v][Hi]".
[head @ .., ExprArg::Pos(Expr::Template(template))] => {
write_args(head);
p.push(']');
template.pretty(p);
}
items => {
write_args(items);
p.push(']');
}
}
}
}
/// The arguments to a function: `12, draw: false`.
///
/// In case of a bracketed invocation with a body, the body is _not_
@ -576,12 +377,6 @@ pub struct ExprArgs {
pub items: Vec<ExprArg>,
}
impl Pretty for ExprArgs {
fn pretty(&self, p: &mut Printer) {
p.join(&self.items, ", ", |item, p| item.pretty(p));
}
}
/// An argument to a function call: `12` or `draw: false`.
#[derive(Debug, Clone, PartialEq)]
pub enum ExprArg {
@ -601,15 +396,6 @@ impl ExprArg {
}
}
impl Pretty for ExprArg {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Pos(expr) => expr.pretty(p),
Self::Named(named) => named.pretty(p),
}
}
}
/// A let expression: `#let x = 1`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprLet {
@ -621,17 +407,6 @@ pub struct ExprLet {
pub init: Option<Box<Expr>>,
}
impl Pretty for ExprLet {
fn pretty(&self, p: &mut Printer) {
p.push_str("#let ");
self.binding.pretty(p);
if let Some(init) = &self.init {
p.push_str(" = ");
init.pretty(p);
}
}
}
/// An if expression: `#if x { y } #else { z }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprIf {
@ -645,19 +420,6 @@ pub struct ExprIf {
pub else_body: Option<Box<Expr>>,
}
impl Pretty for ExprIf {
fn pretty(&self, p: &mut Printer) {
p.push_str("#if ");
self.condition.pretty(p);
p.push(' ');
self.if_body.pretty(p);
if let Some(expr) = &self.else_body {
p.push_str(" #else ");
expr.pretty(p);
}
}
}
/// A for expression: `#for x #in y { z }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprFor {
@ -671,17 +433,6 @@ pub struct ExprFor {
pub body: Box<Expr>,
}
impl Pretty for ExprFor {
fn pretty(&self, p: &mut Printer) {
p.push_str("#for ");
self.pattern.pretty(p);
p.push_str(" #in ");
self.iter.pretty(p);
p.push(' ');
self.body.pretty(p);
}
}
/// A pattern in a for loop.
#[derive(Debug, Clone, PartialEq)]
pub enum ForPattern {
@ -700,16 +451,3 @@ impl ForPattern {
}
}
}
impl Pretty for ForPattern {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Value(v) => v.pretty(p),
Self::KeyValue(k, v) => {
k.pretty(p);
p.push_str(", ");
v.pretty(p);
}
}
}
}

View File

@ -3,7 +3,6 @@ use std::ops::Deref;
use unicode_xid::UnicodeXID;
use super::Span;
use crate::pretty::{Pretty, Printer};
/// An Unicode identifier with a few extra permissible characters.
///
@ -53,12 +52,6 @@ impl Deref for Ident {
}
}
impl Pretty for Ident {
fn pretty(&self, p: &mut Printer) {
p.push_str(self.as_str());
}
}
/// Whether a string is a valid identifier.
pub fn is_ident(string: &str) -> bool {
let mut chars = string.chars();

View File

@ -13,133 +13,5 @@ pub use node::*;
pub use span::*;
pub use token::*;
use crate::pretty::{Pretty, Printer};
/// The abstract syntax tree.
pub type Tree = Vec<Node>;
impl Pretty for Tree {
fn pretty(&self, p: &mut Printer) {
for node in self {
node.pretty(p);
}
}
}
#[cfg(test)]
mod tests {
use crate::parse::parse;
use crate::pretty::pretty;
#[track_caller]
fn test(src: &str, exp: &str) {
let tree = parse(src).output;
let found = pretty(&tree);
if exp != found {
println!("tree: {:#?}", tree);
println!("expected: {}", exp);
println!("found: {}", found);
panic!("test failed");
}
}
#[track_caller]
fn roundtrip(src: &str) {
test(src, src);
}
#[test]
fn test_pretty_print_node() {
// Basic text and markup.
roundtrip("*");
roundtrip("_");
roundtrip(" ");
roundtrip("\\ ");
roundtrip("\n\n");
roundtrip("hi");
// Heading.
roundtrip("= *Ok*");
// Raw.
roundtrip("``");
roundtrip("`nolang 1`");
roundtrip("```lang 1```");
roundtrip("```lang 1 ```");
roundtrip("```hi line ```");
roundtrip("```py\ndef\n```");
roundtrip("```\n line \n```");
roundtrip("```\n`\n```");
roundtrip("``` ` ```");
roundtrip("````\n```\n```\n````");
test("```lang```", "```lang ```");
test("```1 ```", "``");
test("``` 1```", "`1`");
test("``` 1 ```", "`1 `");
test("```` ` ````", "``` ` ```");
}
#[test]
fn test_pretty_print_expr() {
// Basic expressions.
roundtrip("{none}");
roundtrip("{hi}");
roundtrip("{true}");
roundtrip("{10}");
roundtrip("{3.14}");
roundtrip("{10.0pt}");
roundtrip("{14.1deg}");
roundtrip("{20.0%}");
roundtrip("{#abcdef}");
roundtrip(r#"{"hi"}"#);
test(r#"{"let's \" go"}"#, r#"{"let's \" go"}"#);
// Arrays.
roundtrip("{()}");
roundtrip("{(1)}");
roundtrip("{(1, 2, 3)}");
// Dictionaries.
roundtrip("{(:)}");
roundtrip("{(key: value)}");
roundtrip("{(a: 1, b: 2)}");
// Templates.
roundtrip("[]");
roundtrip("[*Ok*]");
roundtrip("{[f]}");
// Groups.
roundtrip("{(1)}");
// Blocks.
roundtrip("{}");
roundtrip("{1}");
roundtrip("{ #let x = 1; x += 2; x + 1 }");
roundtrip("[{}]");
// Operators.
roundtrip("{-x}");
roundtrip("{not true}");
roundtrip("{1 + 3}");
// Parenthesized calls.
roundtrip("{v()}");
roundtrip("{v(1)}");
roundtrip("{v(a: 1, b)}");
// Function templates.
roundtrip("#[v]");
roundtrip("#[v 1]");
roundtrip("#[v 1, 2][*Ok*]");
roundtrip("#[v 1 | f 2]");
test("{#[v]}", "{v()}");
test("#[v 1, #[f 2]]", "#[v 1 | f 2]");
// Keywords.
roundtrip("#let x = 1 + 2");
roundtrip("#if x [y] #else [z]");
roundtrip("#for x #in y {z}");
roundtrip("#for k, x #in y {z}");
}
}

View File

@ -23,30 +23,6 @@ pub enum Node {
Expr(Expr),
}
impl Pretty for Node {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Strong => p.push('*'),
Self::Emph => p.push('_'),
Self::Space => p.push(' '),
Self::Linebreak => p.push_str(r"\"),
Self::Parbreak => p.push_str("\n\n"),
// TODO: Handle escaping.
Self::Text(text) => p.push_str(&text),
Self::Heading(heading) => heading.pretty(p),
Self::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => {
if let Expr::Call(call) = expr {
// Format function templates appropriately.
call.pretty_bracketed(p, false)
} else {
expr.pretty(p);
}
}
}
}
}
/// A section heading: `= Introduction`.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeHeading {
@ -56,15 +32,6 @@ pub struct NodeHeading {
pub contents: Tree,
}
impl Pretty for NodeHeading {
fn pretty(&self, p: &mut Printer) {
for _ in 0 ..= self.level {
p.push('=');
}
self.contents.pretty(p);
}
}
/// A raw block with optional syntax highlighting: `` `raw` ``.
///
/// Raw blocks start with 1 or 3+ backticks and end with the same number of
@ -139,62 +106,3 @@ pub struct NodeRaw {
/// and contains at least one newline.
pub block: bool,
}
impl Pretty for NodeRaw {
fn pretty(&self, p: &mut Printer) {
// Find out how many backticks we need.
let mut backticks = 1;
// Language tag and block-level are only possible with 3+ backticks.
if self.lang.is_some() || self.block {
backticks = 3;
}
// More backticks may be required if there are lots of consecutive
// backticks in the lines.
let mut count;
for line in &self.lines {
count = 0;
for c in line.chars() {
if c == '`' {
count += 1;
backticks = backticks.max(3).max(count + 1);
} else {
count = 0;
}
}
}
// Starting backticks.
for _ in 0 .. backticks {
p.push('`');
}
// Language tag.
if let Some(lang) = &self.lang {
lang.pretty(p);
}
// Start untrimming.
if self.block {
p.push('\n');
} else if backticks >= 3 {
p.push(' ');
}
// The lines.
p.join(&self.lines, "\n", |line, p| p.push_str(line));
// End untrimming.
if self.block {
p.push('\n');
} else if self.lines.last().map_or(false, |line| line.trim_end().ends_with('`')) {
p.push(' ');
}
// Ending backticks.
for _ in 0 .. backticks {
p.push('`');
}
}
}

View File

@ -1,40 +1,11 @@
use std::cell::Cell;
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Range;
use std::ops::{Add, Range};
thread_local! {
static CMP_SPANS: Cell<bool> = Cell::new(true);
}
/// Annotate a value with a span.
pub trait WithSpan: Sized {
/// Wraps `self` in a `Spanned` with the given span.
fn with_span(self, span: impl Into<Span>) -> Spanned<Self> {
Spanned::new(self, span)
}
}
impl<T> WithSpan for T {}
/// Span offsetting.
pub trait Offset {
/// Offset all spans contained in `Self` by the given position.
fn offset(self, by: impl Into<Pos>) -> Self;
}
/// A vector of spanned values of type `T`.
pub type SpanVec<T> = Vec<Spanned<T>>;
impl<T> Offset for SpanVec<T> {
fn offset(mut self, by: impl Into<Pos>) -> Self {
let by = by.into();
for spanned in &mut self {
spanned.span = spanned.span.offset(by);
}
self
}
}
/// A value with the span it corresponds to in the source code.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
@ -68,29 +39,6 @@ impl<T> Spanned<T> {
{
Spanned { v: f(self.v), span: self.span }
}
/// Maps the span while keeping the value.
pub fn map_span<F>(mut self, f: F) -> Self
where
F: FnOnce(Span) -> Span,
{
self.span = f(self.span);
self
}
}
impl<T> Spanned<Option<T>> {
/// Swap the spanned and the option.
pub fn transpose(self) -> Option<Spanned<T>> {
let Spanned { v, span } = self;
v.map(|v| v.with_span(span))
}
}
impl<T> Offset for Spanned<T> {
fn offset(self, by: impl Into<Pos>) -> Self {
self.map_span(|span| span.offset(by))
}
}
impl<T: Debug> Debug for Spanned<T> {
@ -171,16 +119,6 @@ impl Span {
}
}
impl Offset for Span {
fn offset(self, by: impl Into<Pos>) -> Self {
let by = by.into();
Self {
start: self.start.offset(by),
end: self.end.offset(by),
}
}
}
impl Eq for Span {}
impl PartialEq for Span {
@ -234,9 +172,14 @@ impl Pos {
}
}
impl Offset for Pos {
fn offset(self, by: impl Into<Pos>) -> Self {
Pos(self.0 + by.into().0)
impl<T> Add<T> for Pos
where
T: Into<Pos>,
{
type Output = Self;
fn add(self, rhs: T) -> Self {
Pos(self.0 + rhs.into().0)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -25,7 +25,7 @@ use typst::layout::{Element, Expansion, Fill, Frame, Geometry, Image, Shape};
use typst::library;
use typst::parse::{LineMap, Scanner};
use typst::shaping::Shaped;
use typst::syntax::{Location, Pos, SpanVec, Spanned, WithSpan};
use typst::syntax::{Location, Pos, Spanned};
use typst::typeset;
const TYP_DIR: &str = "typ";
@ -266,7 +266,7 @@ fn test_part(
(ok, frames)
}
fn parse_metadata(src: &str, map: &LineMap) -> (Option<bool>, SpanVec<Diag>) {
fn parse_metadata(src: &str, map: &LineMap) -> (Option<bool>, Vec<Spanned<Diag>>) {
let mut diags = vec![];
let mut compare_ref = None;
@ -304,7 +304,8 @@ fn parse_metadata(src: &str, map: &LineMap) -> (Option<bool>, SpanVec<Diag>) {
let mut s = Scanner::new(rest);
let (start, _, end) = (pos(&mut s), s.eat_assert('-'), pos(&mut s));
diags.push(Diag::new(level, s.rest().trim()).with_span(start .. end));
let diag = Diag::new(level, s.rest().trim());
diags.push(Spanned::new(diag, start .. end));
}
diags.sort_by_key(|d| d.span);