added formatting

This commit is contained in:
mlokr 2024-06-25 21:41:12 +02:00
parent 93deeee6b9
commit f9e46b4641
No known key found for this signature in database
GPG key ID: DEA147DDEE644993
4 changed files with 184 additions and 36 deletions

3
hblang/command-help.txt Normal file
View file

@ -0,0 +1,3 @@
--fmt - format all source files
--fmt-current - format mentioned file
--fmt-stdout - dont write the formatted file but print it

View file

@ -1580,7 +1580,9 @@ impl Codegen {
}); });
Some(Value::void()) Some(Value::void())
} }
E::Call { func: fast, args } => { E::Call {
func: fast, args, ..
} => {
let func_ty = self.ty(fast); let func_ty = self.ty(fast);
let ty::Kind::Func(mut func_id) = func_ty.expand() else { let ty::Kind::Func(mut func_id) = func_ty.expand() else {
self.report(fast.pos(), "can't call this, maybe in the future"); self.report(fast.pos(), "can't call this, maybe in the future");

View file

@ -1,12 +1,56 @@
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
let root = std::env::args() let args = std::env::args().collect::<Vec<_>>();
.nth(1) let args = args.iter().map(String::as_str).collect::<Vec<_>>();
.unwrap_or_else(|| "main.hb".to_string()); let root = args.get(1).copied().unwrap_or("main.hb");
let parsed = hblang::parse_from_fs(1, &root)?; if args.contains(&"--help") || args.contains(&"-h") {
let mut codegen = hblang::codegen::Codegen::default(); println!("Usage: hbc [OPTIONS...] <FILE>");
codegen.files = parsed; println!(include_str!("../command-help.txt"));
return Err(std::io::ErrorKind::Other.into());
}
codegen.generate(); let parsed = hblang::parse_from_fs(1, root)?;
codegen.dump(&mut std::io::stdout())
fn format_to_stdout(ast: hblang::parser::Ast) -> std::io::Result<()> {
let source = std::fs::read_to_string(&*ast.path)?;
hblang::parser::with_fmt_source(&source, || {
for expr in ast.exprs() {
use std::io::Write;
writeln!(std::io::stdout(), "{expr}")?;
}
std::io::Result::Ok(())
})
}
fn format_ast(ast: hblang::parser::Ast) -> std::io::Result<()> {
let source = std::fs::read_to_string(&*ast.path)?;
let mut output = Vec::new();
hblang::parser::with_fmt_source(&source, || {
for expr in ast.exprs() {
use std::io::Write;
writeln!(output, "{expr}")?;
}
std::io::Result::Ok(())
})?;
std::fs::write(&*ast.path, output)?;
Ok(())
}
if args.contains(&"--fmt") {
for parsed in parsed {
format_ast(parsed)?;
}
} else if args.contains(&"--fmt-current") {
format_to_stdout(parsed.into_iter().next().unwrap())?;
} else {
let mut codegen = hblang::codegen::Codegen::default();
codegen.files = parsed;
codegen.generate();
codegen.dump(&mut std::io::stdout())?;
}
Ok(())
} }

View file

@ -58,15 +58,16 @@ struct ScopeIdent {
} }
pub struct Parser<'a, 'b> { pub struct Parser<'a, 'b> {
path: &'b str, path: &'b str,
loader: Loader<'b>, loader: Loader<'b>,
lexer: Lexer<'b>, lexer: Lexer<'b>,
arena: &'b Arena<'a>, arena: &'b Arena<'a>,
token: Token, token: Token,
symbols: &'b mut Symbols, symbols: &'b mut Symbols,
ns_bound: usize, ns_bound: usize,
idents: Vec<ScopeIdent>, trailing_sep: bool,
captured: Vec<Ident>, idents: Vec<ScopeIdent>,
captured: Vec<Ident>,
} }
impl<'a, 'b> Parser<'a, 'b> { impl<'a, 'b> Parser<'a, 'b> {
@ -80,6 +81,7 @@ impl<'a, 'b> Parser<'a, 'b> {
arena, arena,
symbols, symbols,
ns_bound: 0, ns_bound: 0,
trailing_sep: false,
idents: Vec::new(), idents: Vec::new(),
captured: Vec::new(), captured: Vec::new(),
} }
@ -384,6 +386,7 @@ impl<'a, 'b> Parser<'a, 'b> {
T::LParen => Expr::Call { T::LParen => Expr::Call {
func: self.arena.alloc(expr), func: self.arena.alloc(expr),
args: self.collect_list(T::Comma, T::RParen, Self::expr), args: self.collect_list(T::Comma, T::RParen, Self::expr),
trailing_comma: std::mem::take(&mut self.trailing_sep),
}, },
T::Ctor => E::Ctor { T::Ctor => E::Ctor {
pos: token.start, pos: token.start,
@ -464,7 +467,7 @@ impl<'a, 'b> Parser<'a, 'b> {
self.collect(|s| { self.collect(|s| {
s.advance_if(end).not().then(|| { s.advance_if(end).not().then(|| {
let val = f(s); let val = f(s);
s.advance_if(delim); s.trailing_sep = s.advance_if(delim);
val val
}) })
}) })
@ -600,6 +603,7 @@ generate_expr! {
Call { Call {
func: &'a Self, func: &'a Self,
args: &'a [Self], args: &'a [Self],
trailing_comma: bool,
}, },
Return { Return {
pos: Pos, pos: Pos,
@ -686,6 +690,17 @@ impl<'a> Poser for &Expr<'a> {
} }
} }
thread_local! {
static FMT_SOURCE: Cell<*const str> = const { Cell::new("") };
}
pub fn with_fmt_source<T>(source: &str, f: impl FnOnce() -> T) -> T {
FMT_SOURCE.with(|s| s.set(source));
let r = f();
FMT_SOURCE.with(|s| s.set(""));
r
}
impl<'a> std::fmt::Display for Expr<'a> { impl<'a> std::fmt::Display for Expr<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
thread_local! { thread_local! {
@ -708,6 +723,33 @@ impl<'a> std::fmt::Display for Expr<'a> {
write!(f, "{end}") write!(f, "{end}")
} }
fn fmt_trailing_list<T>(
f: &mut std::fmt::Formatter,
end: &str,
list: &[T],
fmt: impl Fn(&T, &mut std::fmt::Formatter) -> std::fmt::Result,
) -> std::fmt::Result {
writeln!(f)?;
INDENT.with(|i| i.set(i.get() + 1));
let res = (|| {
for stmt in list {
for _ in 0..INDENT.with(|i| i.get()) {
write!(f, "\t")?;
}
fmt(stmt, f)?;
writeln!(f, ",")?;
}
Ok(())
})();
INDENT.with(|i| i.set(i.get() - 1));
for _ in 0..INDENT.with(|i| i.get()) {
write!(f, "\t")?;
}
write!(f, "{end}")?;
res
}
macro_rules! impl_parenter { macro_rules! impl_parenter {
($($name:ident => $pat:pat,)*) => { ($($name:ident => $pat:pat,)*) => {
$( $(
@ -732,8 +774,21 @@ impl<'a> std::fmt::Display for Expr<'a> {
Consecutive => Expr::UnOp { .. }, Consecutive => Expr::UnOp { .. },
} }
{
let source = unsafe { &*FMT_SOURCE.with(|s| s.get()) };
let pos = self.pos();
if let Some(before) = source.get(..pos as usize) {
let trailing_whitespace = &before[before.trim_end().len()..];
let ncount = trailing_whitespace.chars().filter(|&c| c == '\n').count();
if ncount > 1 {
writeln!(f)?;
}
}
}
match *self { match *self {
Self::Comment { literal, .. } => write!(f, "{literal}"), Self::Comment { literal, .. } => write!(f, "{}", literal.trim_end()),
Self::Mod { path, .. } => write!(f, "@mod(\"{path}\")"), Self::Mod { path, .. } => write!(f, "@mod(\"{path}\")"),
Self::Field { target, field } => write!(f, "{}.{field}", Postfix(target)), Self::Field { target, field } => write!(f, "{}.{field}", Postfix(target)),
Self::Directive { name, args, .. } => { Self::Directive { name, args, .. } => {
@ -787,28 +842,24 @@ impl<'a> std::fmt::Display for Expr<'a> {
fmt_list(f, "", args, |arg, f| write!(f, "{}: {}", arg.name, arg.ty))?; fmt_list(f, "", args, |arg, f| write!(f, "{}: {}", arg.name, arg.ty))?;
write!(f, "): {ret} {body}") write!(f, "): {ret} {body}")
} }
Self::Call { func, args } => { Self::Call {
func,
args,
trailing_comma,
} => {
write!(f, "{}(", Postfix(func))?; write!(f, "{}(", Postfix(func))?;
fmt_list(f, ")", args, std::fmt::Display::fmt) if trailing_comma {
fmt_trailing_list(f, ")", args, std::fmt::Display::fmt)
} else {
fmt_list(f, ")", args, std::fmt::Display::fmt)
}
} }
Self::Return { val: Some(val), .. } => write!(f, "return {val};"), Self::Return { val: Some(val), .. } => write!(f, "return {val};"),
Self::Return { val: None, .. } => write!(f, "return;"), Self::Return { val: None, .. } => write!(f, "return;"),
Self::Ident { name, .. } => write!(f, "{name}"), Self::Ident { name, .. } => write!(f, "{name}"),
Self::Block { stmts, .. } => { Self::Block { stmts, .. } => {
writeln!(f, "{{")?; write!(f, "{{")?;
INDENT.with(|i| i.set(i.get() + 1)); fmt_trailing_list(f, "}", stmts, std::fmt::Display::fmt)
let res = (|| {
for stmt in stmts {
for _ in 0..INDENT.with(|i| i.get()) {
write!(f, " ")?;
}
writeln!(f, "{stmt}")?;
}
Ok(())
})();
INDENT.with(|i| i.set(i.get() - 1));
write!(f, "}}")?;
res
} }
Self::Number { value, .. } => write!(f, "{value}"), Self::Number { value, .. } => write!(f, "{value}"),
Self::Bool { value, .. } => write!(f, "{value}"), Self::Bool { value, .. } => write!(f, "{value}"),
@ -1109,3 +1160,51 @@ impl Drop for ArenaChunk {
} }
} }
} }
#[cfg(test)]
mod test {
fn format(ident: &str, input: &str) {
let ast = super::Ast::new(ident, input, &super::no_loader);
let mut output = String::new();
super::with_fmt_source(input, || {
for expr in ast.exprs() {
use std::fmt::Write;
writeln!(output, "{expr}").unwrap();
}
});
let input_path = format!("formatter_{ident}.expected");
let output_path = format!("formatter_{ident}.actual");
std::fs::write(&input_path, input).unwrap();
std::fs::write(&output_path, output).unwrap();
let success = std::process::Command::new("diff")
.arg("-u")
.arg("--color")
.arg(&input_path)
.arg(&output_path)
.status()
.unwrap()
.success();
std::fs::remove_file(&input_path).unwrap();
std::fs::remove_file(&output_path).unwrap();
assert!(success, "test failed");
}
macro_rules! test {
($($name:ident => $input:expr;)*) => {$(
#[test]
fn $name() {
format(stringify!($name), $input);
}
)*};
}
test! {
comments => "// comment\n// comment\n\n// comment\n\n\
/* comment */\n/* comment */\n\n/* comment */\n";
some_ordinary_code => "loft := fn(): int return loft(1, 2, 3);\n";
some_arg_per_line_code => "loft := fn(): int return loft(\
\n\t1,\n\t2,\n\t3,\n);\n";
}
}