use pretty.rs for formatting

This commit is contained in:
Folkert 2020-04-04 01:41:17 +02:00
parent 3987ed47dc
commit ba16eff24d
4 changed files with 423 additions and 362 deletions

View file

@ -14,6 +14,7 @@ roc_types = { path = "../types" }
roc_load = { path = "../load" }
roc_can = { path = "../can" }
roc_solve = { path = "../solve" }
ven_pretty = { path = "../../vendor/pretty" }
inlinable_string = "0.1.0"
[dev-dependencies]

View file

@ -7,69 +7,48 @@ use roc_types::subs::{Content, Subs};
use roc_types::types::{write_error_type, ErrorType};
use std::path::PathBuf;
// use ven_pretty::termcolor::{Color, ColorChoice, ColorSpec, StandardStream};
use std::fmt;
use ven_pretty::{
BoxAllocator, BoxDoc, DocAllocator, DocBuilder, FmtWrite, Render, RenderAnnotated,
};
type Doc<'a> = DocBuilder<'a, BoxAllocator, Annotation>;
/// A textual report.
pub struct Report {
pub filename: PathBuf,
pub text: ReportText,
}
pub struct Palette {
pub primary: Color,
pub code_block: Color,
pub variable: Color,
pub flex_var: Color,
pub rigid_var: Color,
pub structure: Color,
pub alias: Color,
pub error: Color,
pub line_number: Color,
pub gutter_bar: Color,
pub module_name: Color,
pub binop: Color,
}
#[derive(Copy, Clone)]
pub enum Color {
White,
Red,
Blue,
Yellow,
Green,
Cyan,
Magenta,
pub struct Palette<'a> {
pub primary: &'a str,
pub code_block: &'a str,
pub variable: &'a str,
pub type_variable: &'a str,
pub structure: &'a str,
pub alias: &'a str,
pub error: &'a str,
pub line_number: &'a str,
pub gutter_bar: &'a str,
pub module_name: &'a str,
pub binop: &'a str,
}
pub const TEST_PALETTE: Palette = Palette {
primary: Color::White,
code_block: Color::White,
variable: Color::Blue,
flex_var: Color::Yellow,
rigid_var: Color::Yellow,
structure: Color::Green,
alias: Color::Yellow,
error: Color::Red,
line_number: Color::Cyan,
gutter_bar: Color::Magenta,
module_name: Color::Green,
binop: Color::Green,
primary: WHITE_CODE,
code_block: WHITE_CODE,
variable: BLUE_CODE,
type_variable: YELLOW_CODE,
structure: GREEN_CODE,
alias: YELLOW_CODE,
error: RED_CODE,
line_number: CYAN_CODE,
gutter_bar: MAGENTA_CODE,
module_name: GREEN_CODE,
binop: GREEN_CODE,
};
impl Color {
pub fn render(self, str: &str) -> String {
use Color::*;
match self {
Red => red(str),
White => white(str),
Blue => blue(str),
Yellow => yellow(str),
Green => green(str),
Cyan => cyan(str),
Magenta => magenta(str),
}
}
}
pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
let mut texts = Vec::new();
@ -247,52 +226,6 @@ pub const BOLD_CODE: &str = "\u{001b}[1m";
pub const UNDERLINE_CODE: &str = "\u{001b}[4m";
fn code(code_str: &str, str: &str) -> String {
let mut buf = String::new();
buf.push_str(code_str);
buf.push_str(str);
buf.push_str(RESET_CODE);
buf
}
pub fn underline(str: &str) -> String {
code(UNDERLINE_CODE, str)
}
pub fn bold(str: &str) -> String {
code(BOLD_CODE, str)
}
fn cyan(str: &str) -> String {
code(CYAN_CODE, str)
}
fn magenta(str: &str) -> String {
code(MAGENTA_CODE, str)
}
fn green(str: &str) -> String {
code(GREEN_CODE, str)
}
fn yellow(str: &str) -> String {
code(YELLOW_CODE, str)
}
fn blue(str: &str) -> String {
code(BLUE_CODE, str)
}
fn red(str: &str) -> String {
code(RED_CODE, str)
}
fn white(str: &str) -> String {
code(WHITE_CODE, str)
}
pub const RESET_CODE: &str = "\u{001b}[0m";
struct CiEnv<'a> {
@ -301,6 +234,207 @@ struct CiEnv<'a> {
interns: &'a Interns,
}
pub struct CiWrite<W> {
style_stack: Vec<Annotation>,
upstream: W,
}
impl<W> CiWrite<W> {
pub fn new(upstream: W) -> CiWrite<W> {
CiWrite {
style_stack: vec![],
upstream,
}
}
}
pub struct ColorWrite<'a, W> {
style_stack: Vec<Annotation>,
palette: &'a Palette<'a>,
upstream: W,
}
impl<'a, W> ColorWrite<'a, W> {
pub fn new(palette: &'a Palette, upstream: W) -> ColorWrite<'a, W> {
ColorWrite {
style_stack: vec![],
palette,
upstream,
}
}
}
#[derive(Copy, Clone)]
pub enum Annotation {
Emphasized,
Url,
Keyword,
GlobalTag,
PrivateTag,
RecordField,
TypeVariable,
Alias,
Structure,
Symbol,
BinOp,
Error,
GutterBar,
LineNumber,
PlainText,
CodeBlock,
Module,
}
impl<W> Render for CiWrite<W>
where
W: fmt::Write,
{
type Error = fmt::Error;
fn write_str(&mut self, s: &str) -> Result<usize, fmt::Error> {
self.write_str_all(s).map(|_| s.len())
}
fn write_str_all(&mut self, s: &str) -> fmt::Result {
self.upstream.write_str(s)
}
}
impl<W> RenderAnnotated<Annotation> for CiWrite<W>
where
W: fmt::Write,
{
fn push_annotation(&mut self, annotation: &Annotation) -> Result<(), Self::Error> {
use Annotation::*;
match annotation {
Emphasized => {
self.write_str("*")?;
}
Url => {
self.write_str("<")?;
}
GlobalTag | PrivateTag | RecordField | Keyword => {
self.write_str("`")?;
}
CodeBlock | PlainText | LineNumber | Error | GutterBar | TypeVariable | Alias
| Module | Structure | Symbol | BinOp => {}
}
self.style_stack.push(*annotation);
Ok(())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
use Annotation::*;
match self.style_stack.pop() {
None => {}
Some(annotation) => match annotation {
Emphasized => {
self.write_str("*")?;
}
Url => {
self.write_str(">")?;
}
GlobalTag | PrivateTag | RecordField | Keyword => {
self.write_str("`")?;
}
CodeBlock | PlainText | LineNumber | Error | GutterBar | TypeVariable | Alias
| Module | Structure | Symbol | BinOp => {}
},
}
Ok(())
}
}
impl<'a, W> Render for ColorWrite<'a, W>
where
W: fmt::Write,
{
type Error = fmt::Error;
fn write_str(&mut self, s: &str) -> Result<usize, fmt::Error> {
self.write_str_all(s).map(|_| s.len())
}
fn write_str_all(&mut self, s: &str) -> fmt::Result {
self.upstream.write_str(s)
}
}
impl<'a, W> RenderAnnotated<Annotation> for ColorWrite<'a, W>
where
W: fmt::Write,
{
fn push_annotation(&mut self, annotation: &Annotation) -> Result<(), Self::Error> {
use Annotation::*;
match annotation {
Emphasized => {
self.write_str(BOLD_CODE)?;
}
Url => {
self.write_str(UNDERLINE_CODE)?;
}
PlainText => {
self.write_str(self.palette.primary)?;
}
CodeBlock => {
self.write_str(self.palette.code_block)?;
}
TypeVariable => {
self.write_str(self.palette.type_variable)?;
}
Alias => {
self.write_str(self.palette.alias)?;
}
BinOp => {
self.write_str(self.palette.alias)?;
}
Symbol => {
self.write_str(self.palette.variable)?;
}
GutterBar => {
self.write_str(self.palette.gutter_bar)?;
}
Error => {
self.write_str(self.palette.error)?;
}
LineNumber => {
self.write_str(self.palette.line_number)?;
}
Structure => {
self.write_str(self.palette.structure)?;
}
Module => {
self.write_str(self.palette.module_name)?;
}
GlobalTag | PrivateTag | RecordField | Keyword => {
self.write_str("`")?;
}
}
self.style_stack.push(*annotation);
Ok(())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
use Annotation::*;
match self.style_stack.pop() {
None => {}
Some(annotation) => match annotation {
Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar
| Structure | CodeBlock | PlainText | LineNumber | Module => {
self.write_str(RESET_CODE)?;
}
GlobalTag | PrivateTag | RecordField | Keyword => {
self.write_str("`")?;
}
},
}
Ok(())
}
}
impl ReportText {
/// Render to CI console output, where no colors are available.
pub fn render_ci(
@ -311,161 +445,14 @@ impl ReportText {
src_lines: &[&str],
interns: &Interns,
) {
let env = CiEnv {
home,
src_lines,
interns,
};
let allocator = BoxAllocator;
self.render_ci_help(&env, buf, subs, 0);
}
let err_msg = "<buffer is not a utf-8 encoded string>";
fn render_ci_help(self, env: &CiEnv, buf: &mut String, subs: &mut Subs, indent: usize) {
use ReportText::*;
match self {
Plain(string) => buf.push_str(&string),
EmText(string) => {
// Since this is CI, the best we can do for emphasis are asterisks.
buf.push('*');
buf.push_str(&string);
buf.push('*');
}
GlobalTag(string) | Keyword(string) => {
// Since this is CI, the best we can do for code text is backticks.
buf.push('`');
buf.push_str(&string);
buf.push('`');
}
RecordField(string) => {
// Since this is CI, the best we can do for code text is backticks.
buf.push('`');
buf.push('.');
buf.push_str(&string);
buf.push('`');
}
Url(url) => {
buf.push('<');
buf.push_str(&url);
buf.push('>');
}
PrivateTag(symbol) | Value(symbol) => {
if symbol.module_id() == env.home {
// Render it unqualified if it's in the current module.
buf.push_str(symbol.ident_string(env.interns));
} else {
buf.push_str(symbol.module_string(env.interns));
buf.push('.');
buf.push_str(symbol.ident_string(env.interns));
}
}
Module(module_id) => {
buf.push_str(&env.interns.module_name(module_id));
}
Type(content) => {
buf.push_str(content_to_string(content, subs, env.home, env.interns).as_str())
}
ErrorType(error_type) => {
buf.push('\n');
buf.push_str(" ".repeat(indent).as_str());
buf.push_str(&write_error_type(env.home, env.interns, error_type));
buf.push('\n');
}
Region(region) => {
buf.push('\n');
buf.push('\n');
// widest displayed line number
let max_line_number_length = (region.end_line + 1).to_string().len();
if region.start_line == region.end_line {
let i = region.start_line;
let line_number_string = (i + 1).to_string();
let line_number = line_number_string.as_str();
let this_line_number_length = line_number.len();
buf.push_str(
" ".repeat(max_line_number_length - this_line_number_length)
.as_str(),
);
buf.push_str(line_number);
buf.push_str("");
let line = env.src_lines[i as usize];
if !line.trim().is_empty() {
buf.push_str(" ");
buf.push_str(env.src_lines[i as usize]);
}
buf.push('\n');
buf.push_str(" ".repeat(max_line_number_length).as_str());
buf.push_str("");
buf.push_str(" ".repeat(region.start_col as usize + 2).as_str());
buf.push_str(
"^".repeat((region.end_col - region.start_col) as usize)
.as_str(),
);
} else {
for i in region.start_line..=region.end_line {
let i_one_indexed = i + 1;
let line_number_string = i_one_indexed.to_string();
let line_number = line_number_string.as_str();
let this_line_number_length = line_number.len();
buf.push_str(
" ".repeat(max_line_number_length - this_line_number_length)
.as_str(),
);
buf.push_str(line_number);
buf.push_str(" ┆>");
let line = env.src_lines[i as usize];
if !line.trim().is_empty() {
buf.push_str(" ");
buf.push_str(env.src_lines[i as usize]);
}
if i != region.end_line {
buf.push('\n');
}
}
}
buf.push('\n');
buf.push('\n');
}
Indent(n, nested) => {
nested.render_ci_help(env, buf, subs, indent + n);
}
Docs(_) => {
panic!("TODO implment docs");
}
Concat(report_texts) => {
for report_text in report_texts {
report_text.render_ci_help(env, buf, subs, indent);
}
}
Stack(report_texts) => {
let mut it = report_texts.into_iter().peekable();
while let Some(report_text) = it.next() {
report_text.render_ci_help(env, buf, subs, indent);
buf.push('\n');
if it.peek().is_some() {
buf.push_str(" ".repeat(indent).as_str());
}
}
}
BinOp(bin_op) => {
buf.push_str(bin_op.to_string().as_str());
}
}
self.pretty::<_>(&allocator, subs, home, src_lines, interns)
.1
.render_raw(70, &mut CiWrite::new(buf))
.expect(err_msg);
}
/// Render to a color terminal using ANSI escape sequences
@ -478,142 +465,219 @@ impl ReportText {
interns: &Interns,
palette: &Palette,
) {
let allocator = BoxAllocator;
let err_msg = "<buffer is not a utf-8 encoded string>";
self.pretty::<_>(&allocator, subs, home, src_lines, interns)
.1
.render_raw(70, &mut ColorWrite::new(palette, buf))
.expect(err_msg);
}
/// General idea: this function puts all the characters in. Any styling (emphasis, colors,
/// monospace font, etc) is done in the CiWrite and ColorWrite `RenderAnnotated` instances.
pub fn pretty<'b, D>(
self,
allocator: &'b D,
subs: &mut Subs,
home: ModuleId,
src_lines: &'b [&'b str],
interns: &Interns,
) -> DocBuilder<'b, D, Annotation>
where
D: DocAllocator<'b, Annotation>,
D::Doc: Clone,
{
use ReportText::*;
match self {
Plain(string) => {
buf.push_str(&palette.primary.render(&string));
}
EmText(string) => {
buf.push_str(&bold(&string));
}
Url(url) => {
buf.push_str(&underline(&url));
Plain(string) => allocator
.text(format!("{}", string))
.annotate(Annotation::PlainText),
EmText(string) => allocator
.text(format!("{}", string))
.annotate(Annotation::Emphasized),
Url(url) => allocator.text(format!("{}", url)).annotate(Annotation::Url),
Keyword(string) => allocator
.text(format!("{}", string))
.annotate(Annotation::Keyword),
GlobalTag(string) => allocator
.text(format!("{}", string))
.annotate(Annotation::GlobalTag),
RecordField(string) => allocator
.text(format!(".{}", string))
.annotate(Annotation::RecordField),
PrivateTag(symbol) => {
if symbol.module_id() == home {
// Render it unqualified if it's in the current module.
allocator
.text(format!("{}", symbol.ident_string(interns)))
.annotate(Annotation::PrivateTag)
} else {
allocator
.text(format!(
"{}.{}",
symbol.module_string(interns),
symbol.ident_string(interns),
))
.annotate(Annotation::PrivateTag)
}
}
Value(symbol) => {
if symbol.module_id() == home {
// Render it unqualified if it's in the current module.
buf.push_str(&palette.variable.render(symbol.ident_string(interns)));
allocator
.text(format!("{}", symbol.ident_string(interns)))
.annotate(Annotation::Symbol)
} else {
let mut module_str = String::new();
module_str.push_str(symbol.module_string(interns));
module_str.push('.');
module_str.push_str(symbol.ident_string(interns));
buf.push_str(&palette.variable.render(&module_str));
allocator
.text(format!(
"{}.{}",
symbol.module_string(interns),
symbol.ident_string(interns),
))
.annotate(Annotation::Symbol)
}
}
Module(module_id) => {
buf.push_str(&palette.module_name.render(&interns.module_name(module_id)));
}
Module(module_id) => allocator
.text(format!("{}", interns.module_name(module_id)))
.annotate(Annotation::Module),
Type(content) => match content {
Content::FlexVar(flex_var) => buf.push_str(&palette.flex_var.render(
content_to_string(Content::FlexVar(flex_var), subs, home, interns).as_str(),
)),
Content::RigidVar(rigid_var) => buf.push_str(&palette.rigid_var.render(
content_to_string(Content::RigidVar(rigid_var), subs, home, interns).as_str(),
)),
Content::Structure(structure) => buf.push_str(&palette.structure.render(
// TODO give greater specificity to how structures are colored. Empty record colored differently than tags, etc.
content_to_string(Content::Structure(structure), subs, home, interns).as_str(),
)),
Content::Alias(symbol, vars, var) => buf.push_str(
&palette.alias.render(
content_to_string(Content::Alias(symbol, vars, var), subs, home, interns)
.as_str(),
),
),
Content::Error => {}
Content::FlexVar(_) | Content::RigidVar(_) => allocator
.text(content_to_string(content, subs, home, interns))
.annotate(Annotation::TypeVariable),
Content::Structure(_) => allocator
.text(content_to_string(content, subs, home, interns))
.annotate(Annotation::Structure),
Content::Alias(_, _, _) => allocator
.text(content_to_string(content, subs, home, interns))
.annotate(Annotation::Alias),
Content::Error => allocator.text(content_to_string(content, subs, home, interns)),
},
ErrorType(error_type) => buf.push_str(&write_error_type(home, interns, error_type)),
ErrorType(error_type) => allocator
.nil()
.append(allocator.hardline())
.append(
allocator
.text(write_error_type(home, interns, error_type))
.indent(4),
)
.append(allocator.hardline()),
Indent(n, nested) => {
let rest = nested.pretty(allocator, subs, home, src_lines, interns);
allocator.nil().append(rest).indent(n)
}
Docs(_) => {
panic!("TODO implment docs");
}
Concat(report_texts) => allocator.concat(
report_texts
.into_iter()
.map(|rep| rep.pretty(allocator, subs, home, src_lines, interns)),
),
Stack(report_texts) => allocator.intersperse(
report_texts
.into_iter()
.map(|rep| (rep.pretty(allocator, subs, home, src_lines, interns))),
allocator.hardline(),
),
BinOp(bin_op) => allocator
.text(bin_op.to_string())
.annotate(Annotation::BinOp),
Region(region) => {
// newline before snippet
buf.push('\n');
buf.push('\n');
// the widest line number that is rendered
let max_line_number_length = (region.end_line + 1).to_string().len();
let indent = 2;
if region.start_line == region.end_line {
// single line
let body = if region.start_line == region.end_line {
let i = region.start_line;
let i_one_indexed = i + 1;
let line_number_string = i_one_indexed.to_string();
let line_number = line_number_string.as_str();
let line_number_string = (i + 1).to_string();
let line_number = line_number_string;
let this_line_number_length = line_number.len();
buf.push_str(
" ".repeat(max_line_number_length - this_line_number_length)
.as_str(),
);
buf.push_str(&palette.line_number.render(line_number));
buf.push_str(&palette.gutter_bar.render(""));
let line = src_lines[i as usize];
let rest_of_line = if line.trim().is_empty() {
allocator.nil()
} else {
allocator
.nil()
.append(allocator.text(line).indent(2))
.annotate(Annotation::CodeBlock)
};
if !line.trim().is_empty() {
buf.push_str(" ");
buf.push_str(&palette.code_block.render(src_lines[i as usize]));
}
let source_line = allocator
.line()
.append(
allocator
.text(" ".repeat(max_line_number_length - this_line_number_length)),
)
.append(allocator.text(line_number).annotate(Annotation::LineNumber))
.append(allocator.text("").annotate(Annotation::GutterBar))
.append(rest_of_line);
buf.push('\n');
buf.push_str(" ".repeat(max_line_number_length).as_str());
buf.push_str(&palette.gutter_bar.render(""));
let highlight_line = allocator
.line()
.append(allocator.text(" ".repeat(max_line_number_length)))
.append(allocator.text("").annotate(Annotation::GutterBar))
.append(
allocator
.text(" ".repeat(region.start_col as usize))
.indent(indent),
)
.append(
allocator
.text("^".repeat((region.end_col - region.start_col) as usize))
.annotate(Annotation::Error),
);
buf.push_str(" ".repeat(region.start_col as usize + 2).as_str());
let carets = "^".repeat((region.end_col - region.start_col) as usize);
buf.push_str(&palette.error.render(carets.as_str()));
source_line.append(highlight_line)
} else {
// multiline
let mut result = allocator.nil();
for i in region.start_line..=region.end_line {
let i_one_indexed = i + 1;
let line_number_string = i_one_indexed.to_string();
let line_number = line_number_string.as_str();
let line_number_string = (i + 1).to_string();
let line_number = line_number_string;
let this_line_number_length = line_number.len();
buf.push_str(
" ".repeat(max_line_number_length - this_line_number_length)
.as_str(),
);
buf.push_str(&palette.line_number.render(line_number));
buf.push_str(&palette.gutter_bar.render(""));
buf.push_str(&palette.error.render(">"));
let line = src_lines[i as usize];
let rest_of_line = if !line.trim().is_empty() {
allocator
.text(line)
.annotate(Annotation::CodeBlock)
.indent(indent)
} else {
allocator.nil()
};
if !line.trim().is_empty() {
buf.push_str(" ");
buf.push_str(&palette.code_block.render(src_lines[i as usize]));
}
let source_line = allocator
.line()
.append(
allocator.text(
" ".repeat(max_line_number_length - this_line_number_length),
),
)
.append(allocator.text(line_number).annotate(Annotation::LineNumber))
.append(allocator.text("").annotate(Annotation::GutterBar))
.append(allocator.text(">").annotate(Annotation::Error))
.append(rest_of_line);
if i != region.end_line {
buf.push('\n');
}
result = result.append(source_line);
}
}
// newline before next line of text
buf.push('\n');
buf.push('\n');
result
};
allocator
.nil()
.append(allocator.line())
.append(body)
.append(allocator.line())
.append(allocator.line())
}
Indent(n, nested) => {
buf.push_str(" ".repeat(n).as_str());
nested.render_color_terminal(buf, subs, home, src_lines, interns, palette);
}
Concat(report_texts) => {
for report_text in report_texts {
report_text.render_color_terminal(buf, subs, home, src_lines, interns, palette);
}
}
BinOp(bin_op) => {
buf.push_str(&palette.binop.render(bin_op.to_string().as_str()));
}
_ => panic!("TODO implement more ReportTexts in render color terminal"),
}
}
}

View file

@ -253,9 +253,9 @@ fn type_comparison(
ReportText::Stack(vec![
i_am_seeing,
with_indent(4, comparison.actual),
comparison.actual,
instead_of,
with_indent(4, comparison.expected),
comparison.expected,
context_hints,
problems_to_hint(comparison.problems),
])
@ -271,7 +271,7 @@ fn lone_type(
ReportText::Stack(vec![
i_am_seeing,
with_indent(4, comparison.actual),
comparison.actual,
further_details,
problems_to_hint(comparison.problems),
])
@ -372,7 +372,7 @@ fn to_circular_report(
Region(region),
Stack(vec![
plain_text("Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely."),
with_indent(4, type_in_focus(overall_type)),
type_in_focus(overall_type),
/* TODO hint */
]),
];

View file

@ -776,7 +776,6 @@ mod test_reporting {
Str
but I need every `if` condition to evaluate to a Booleither `True` or `False`.
"#
),
)
@ -806,7 +805,6 @@ mod test_reporting {
Num a
instead. I need all branches in an `if` to have the same type!
"#
),
)
@ -862,7 +860,6 @@ mod test_reporting {
Here is my best effort at writing down the type. You will see for parts of the type that repeat something already printed out infinitely.
-> a
"#
),
)
@ -888,7 +885,6 @@ mod test_reporting {
Here is my best effort at writing down the type. You will see for parts of the type that repeat something already printed out infinitely.
List -> a
"#
),
)