feat: bring over the formatter from gleam

This commit is contained in:
rvcas
2022-11-01 19:53:19 -04:00
parent 91a131d520
commit cba7a6f46e
32 changed files with 2270 additions and 480 deletions

View File

@@ -10,6 +10,7 @@ authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
[dependencies]
aiken-lang = { path = "../lang", version = "0.0.24" }
ignore = "0.4.18"
miette = { version = "5.3.0", features = ["fancy"] }
petgraph = "0.6.2"
regex = "1.6.0"

View File

@@ -1,10 +1,10 @@
use std::{
fmt::{Debug, Display},
io,
path::PathBuf,
path::{Path, PathBuf},
};
use aiken_lang::{error::ParseError, tipo};
use aiken_lang::{parser::error::ParseError, tipo};
use miette::{Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, RgbColors, SourceCode};
#[allow(dead_code)]
@@ -20,6 +20,12 @@ pub enum Error {
#[error("file operation failed")]
FileIo { error: io::Error, path: PathBuf },
#[error("source code incorrectly formatted")]
Format { problem_files: Vec<Unformatted> },
#[error(transparent)]
StandardIo(#[from] io::Error),
#[error("cyclical module imports")]
ImportCycle { modules: Vec<String> },
@@ -64,6 +70,20 @@ impl Error {
rest => eprintln!("Error: {:?}", rest),
}
}
pub fn from_parse_errors(errs: Vec<ParseError>, path: &Path, src: &str) -> Self {
let mut errors = Vec::with_capacity(errs.len());
for error in errs {
errors.push(Error::Parse {
path: path.into(),
src: src.to_string(),
error: error.into(),
});
}
Error::List(errors)
}
}
impl Debug for Error {
@@ -94,6 +114,8 @@ impl Diagnostic for Error {
Error::List(_) => None,
Error::Parse { .. } => Some(Box::new("aiken::parser")),
Error::Type { .. } => Some(Box::new("aiken::typecheck")),
Error::StandardIo(_) => None,
Error::Format { .. } => None,
}
}
@@ -112,6 +134,8 @@ impl Diagnostic for Error {
Error::List(_) => None,
Error::Parse { error, .. } => error.kind.help(),
Error::Type { error, .. } => error.help(),
Error::StandardIo(_) => None,
Error::Format { .. } => None,
}
}
@@ -123,6 +147,8 @@ impl Diagnostic for Error {
Error::List(_) => None,
Error::Parse { error, .. } => error.labels(),
Error::Type { error, .. } => error.labels(),
Error::StandardIo(_) => None,
Error::Format { .. } => None,
}
}
@@ -134,6 +160,8 @@ impl Diagnostic for Error {
Error::List(_) => None,
Error::Parse { src, .. } => Some(src),
Error::Type { src, .. } => Some(src),
Error::StandardIo(_) => None,
Error::Format { .. } => None,
}
}
}
@@ -196,3 +224,11 @@ impl Debug for Warning {
Ok(())
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Unformatted {
pub source: PathBuf,
pub destination: PathBuf,
pub input: String,
pub output: String,
}

View File

@@ -0,0 +1,147 @@
use std::{
fs,
io::Read,
path::{Path, PathBuf},
str::FromStr,
};
use aiken_lang::{ast::ModuleKind, parser};
use crate::{
error::{Error, Unformatted},
is_aiken_path,
};
pub fn run(stdin: bool, check: bool, files: Vec<String>) -> Result<(), Error> {
if stdin {
process_stdin(check)
} else {
process_files(check, files)
}
}
fn process_stdin(check: bool) -> Result<(), Error> {
let src = read_stdin()?;
let mut out = String::new();
let (module, extra) = parser::module(&src, ModuleKind::Lib)
.map_err(|errs| Error::from_parse_errors(errs, Path::new("<stdin>"), &src))?;
aiken_lang::format::pretty(&mut out, module, extra, &src);
if !check {
print!("{}", out);
return Ok(());
}
if src != out {
return Err(Error::Format {
problem_files: vec![Unformatted {
source: PathBuf::from("<standard input>"),
destination: PathBuf::from("<standard output>"),
input: src,
output: out,
}],
});
}
Ok(())
}
fn process_files(check: bool, files: Vec<String>) -> Result<(), Error> {
if check {
check_files(files)
} else {
format_files(files)
}
}
fn check_files(files: Vec<String>) -> Result<(), Error> {
let problem_files = unformatted_files(files)?;
if problem_files.is_empty() {
Ok(())
} else {
Err(Error::Format { problem_files })
}
}
fn format_files(files: Vec<String>) -> Result<(), Error> {
for file in unformatted_files(files)? {
fs::write(file.destination, file.output)?;
}
Ok(())
}
fn unformatted_files(files: Vec<String>) -> Result<Vec<Unformatted>, Error> {
let mut problem_files = Vec::with_capacity(files.len());
let mut errors = Vec::new();
for file_path in files {
let path = PathBuf::from_str(&file_path).unwrap();
if path.is_dir() {
for path in aiken_files_excluding_gitignore(&path) {
if let Err(err) = format_file(&mut problem_files, path) {
errors.push(err);
};
}
} else if let Err(err) = format_file(&mut problem_files, path) {
println!("{:?}", err);
errors.push(err);
}
}
if errors.is_empty() {
Ok(problem_files)
} else {
Err(Error::List(errors))
}
}
fn format_file(problem_files: &mut Vec<Unformatted>, path: PathBuf) -> Result<(), Error> {
let src = fs::read_to_string(&path).map_err(|error| Error::FileIo {
error,
path: path.clone(),
})?;
let mut output = String::new();
let (module, extra) = parser::module(&src, ModuleKind::Lib)
.map_err(|errs| Error::from_parse_errors(errs, &path, &src))?;
aiken_lang::format::pretty(&mut output, module, extra, &src);
if src != output {
problem_files.push(Unformatted {
source: path.clone(),
destination: path,
input: src,
output,
});
}
Ok(())
}
pub fn read_stdin() -> Result<String, Error> {
let mut src = String::new();
std::io::stdin().read_to_string(&mut src)?;
Ok(src)
}
pub fn aiken_files_excluding_gitignore(dir: &Path) -> impl Iterator<Item = PathBuf> + '_ {
ignore::WalkBuilder::new(dir)
.follow_links(true)
.require_git(false)
.build()
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false))
.map(ignore::DirEntry::into_path)
.filter(move |d| is_aiken_path(d, dir))
}

View File

@@ -6,6 +6,7 @@ use std::{
pub mod config;
pub mod error;
pub mod format;
pub mod module;
use aiken_lang::{ast::ModuleKind, builtins, tipo::TypeInfo, IdGenerator};
@@ -106,8 +107,8 @@ impl Project {
kind,
} in self.sources.drain(0..)
{
match aiken_lang::parser::script(&code) {
Ok(mut ast) => {
match aiken_lang::parser::module(&code, kind) {
Ok((mut ast, _)) => {
// Store the name
ast.name = name.clone();