Merge pull request #210 from aiken-lang/fix-if-expressions

This commit is contained in:
Lucas 2022-12-22 12:46:24 -05:00 committed by GitHub
commit 168196f903
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 295 additions and 75 deletions

View File

@ -6,10 +6,10 @@ use vec1::Vec1;
use crate::{ use crate::{
ast::{ ast::{
Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, ClauseGuard, Constant, DataType, Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, ClauseGuard, Constant, DataType,
Definition, Function, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg, Definition, Function, IfBranch, ModuleConstant, Pattern, RecordConstructor,
RecordUpdateSpread, Span, TypeAlias, TypedArg, TypedConstant, UnqualifiedImport, RecordConstructorArg, RecordUpdateSpread, Span, TypeAlias, TypedArg, TypedConstant,
UntypedArg, UntypedClause, UntypedClauseGuard, UntypedDefinition, UntypedModule, UnqualifiedImport, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedDefinition,
UntypedPattern, UntypedRecordUpdateArg, Use, CAPTURE_VARIABLE, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, CAPTURE_VARIABLE,
}, },
docvec, docvec,
expr::UntypedExpr, expr::UntypedExpr,
@ -133,14 +133,14 @@ impl<'comments> Formatter<'comments> {
let start = def.location().start; let start = def.location().start;
match def { match def {
Definition::Use { .. } => { Definition::Use(import) => {
has_imports = true; has_imports = true;
let comments = self.pop_comments(start); let comments = self.pop_comments(start);
let def = self.definition(def); let def = self.definition(def);
imports.push(commented(def, comments)) imports.push((import, commented(def, comments)))
} }
_other => { _other => {
@ -155,7 +155,15 @@ impl<'comments> Formatter<'comments> {
} }
} }
let imports = join(imports.into_iter(), line()); let imports = join(
imports
.into_iter()
.sorted_by(|(import_a, _), (import_b, _)| {
Ord::cmp(&import_a.module, &import_b.module)
})
.map(|(_, doc)| doc),
line(),
);
let declarations = join(declarations.into_iter(), lines(2)); let declarations = join(declarations.into_iter(), lines(2));
@ -253,12 +261,35 @@ impl<'comments> Formatter<'comments> {
.. ..
}) => self.data_type(*public, *opaque, name, parameters, constructors, location), }) => self.data_type(*public, *opaque, name, parameters, constructors, location),
Definition::Use(Use { Definition::Use(import) => self.import(import),
Definition::ModuleConstant(ModuleConstant {
public,
name,
annotation,
value,
..
}) => {
let head = pub_(*public).append("const ").append(name.as_str());
let head = match annotation {
None => head,
Some(t) => head.append(": ").append(self.annotation(t)),
};
head.append(" = ").append(self.const_expr(value))
}
}
}
fn import<'a>(
&mut self,
Use {
module, module,
as_name, as_name,
unqualified, unqualified,
.. ..
}) => "use " }: &'a Use<()>,
) -> Document<'a> {
"use "
.to_doc() .to_doc()
.append(Document::String(module.join("/"))) .append(Document::String(module.join("/")))
.append(if unqualified.is_empty() { .append(if unqualified.is_empty() {
@ -282,23 +313,7 @@ impl<'comments> Formatter<'comments> {
docvec![" as ", name] docvec![" as ", name]
} else { } else {
nil() nil()
}), })
Definition::ModuleConstant(ModuleConstant {
public,
name,
annotation,
value,
..
}) => {
let head = pub_(*public).append("const ").append(name.as_str());
let head = match annotation {
None => head,
Some(t) => head.append(": ").append(self.annotation(t)),
};
head.append(" = ").append(self.const_expr(value))
}
}
} }
fn const_expr<'a, A, B>(&mut self, value: &'a Constant<A, B>) -> Document<'a> { fn const_expr<'a, A, B>(&mut self, value: &'a Constant<A, B>) -> Document<'a> {
@ -663,44 +678,7 @@ impl<'comments> Formatter<'comments> {
branches, branches,
final_else, final_else,
.. ..
} => { } => self.if_expr(branches, final_else),
let first = branches.first();
break_("if", "if ")
.append(self.wrap_expr(&first.condition))
.nest(INDENT)
.append(break_("", " "))
.append("{")
.group()
.append(line())
.nest(INDENT)
.append(self.expr(&first.body))
.append(line())
.append("} ")
.append(join(
branches[1..].iter().map(|branch| {
break_("else if", "else if ")
.append(self.wrap_expr(&branch.condition))
.nest(INDENT)
.append(break_("", " "))
.append("{")
.group()
.append(line())
.nest(INDENT)
.append(self.expr(&branch.body))
.append(line())
.append("} ")
}),
nil(),
))
.append("else {")
.group()
.append(line().nest(INDENT))
.append(self.expr(final_else))
.append(line())
.append("}")
.force_break()
}
UntypedExpr::Todo { label: None, .. } => "todo".to_doc(), UntypedExpr::Todo { label: None, .. } => "todo".to_doc(),
UntypedExpr::Todo { label: Some(l), .. } => docvec!["todo(\"", l, "\")"], UntypedExpr::Todo { label: Some(l), .. } => docvec!["todo(\"", l, "\")"],
@ -917,6 +895,48 @@ impl<'comments> Formatter<'comments> {
} }
} }
pub fn if_expr<'a>(
&mut self,
branches: &'a Vec1<IfBranch<UntypedExpr>>,
final_else: &'a UntypedExpr,
) -> Document<'a> {
let if_branches = self
.if_branch(break_("if", "if "), branches.first())
.append(join(
branches[1..]
.iter()
.map(|branch| self.if_branch("else if".to_doc(), branch)),
nil(),
));
let else_begin = line().append("} else {");
let else_body = line().append(self.expr(final_else)).nest(INDENT);
let else_end = line().append("}");
if_branches
.append(else_begin)
.append(else_body)
.append(else_end)
.force_break()
}
pub fn if_branch<'a>(
&mut self,
if_keyword: Document<'a>,
branch: &'a IfBranch<UntypedExpr>,
) -> Document<'a> {
let if_begin = if_keyword
.append(self.wrap_expr(&branch.condition))
.append(break_("{", " {"))
.group();
let if_body = line().append(self.expr(&branch.body)).nest(INDENT);
if_begin.append(if_body)
}
pub fn when<'a>( pub fn when<'a>(
&mut self, &mut self,
subjects: &'a [UntypedExpr], subjects: &'a [UntypedExpr],

View File

@ -0,0 +1,199 @@
use crate::{ast::ModuleKind, format, parser};
use indoc::indoc;
use pretty_assertions::assert_eq;
fn assert_fmt(src: &str, expected: &str) {
let (module, extra) = parser::module(src, ModuleKind::Lib).unwrap();
let mut out = String::new();
format::pretty(&mut out, module, extra, src);
// Output is what we expect
assert_eq!(out, expected);
// Formatting is idempotent
let (module2, extra2) = parser::module(&out, ModuleKind::Lib).unwrap();
let mut out2 = String::new();
format::pretty(&mut out2, module2, extra2, &out);
assert_eq!(out, out2);
}
#[test]
fn test_format_if() {
let src = indoc! {r#"
pub fn foo(a) {
if a { 14 } else { 42 }
}
pub fn bar(xs) {
list.map(xs, fn (x) { if x > 0 { "foo" } else { "bar" } })
}
"#};
let expected = indoc! {r#"
pub fn foo(a) {
if a {
14
} else {
42
}
}
pub fn bar(xs) {
list.map(
xs,
fn(x) {
if x > 0 {
"foo"
} else {
"bar"
}
},
)
}
"#};
assert_fmt(src, expected)
}
#[test]
fn test_format_when() {
let src = indoc! {r#"
pub fn foo( a) {
when a is{
True -> 14
False ->
42}
}
"#};
let expected = indoc! {r#"
pub fn foo(a) {
when a is {
True -> 14
False -> 42
}
}
"#};
assert_fmt(src, expected)
}
#[test]
fn test_format_nested_if() {
let src = indoc! {r#"
pub fn foo(n) {
if n > 0 {
if n > 1 {
if n > 2 {
"foo"
} else {
"foo"
}
} else {
"bar"
}
} else {
if n < -1 {
"baz"
} else {
"biz"
}
}
}
"#};
assert_fmt(src, src)
}
#[test]
fn test_format_nested_when_if() {
let src = indoc! {r#"
pub fn drop(xs: List<a>, n: Int) -> List<a> {
if n <= 0 {
xs
} else {
when xs is {
[] -> []
[_x, ..rest] -> drop(rest, n - 1)
}
}
}
"#};
let expected = indoc! {r#"
pub fn drop(xs: List<a>, n: Int) -> List<a> {
if n <= 0 {
xs
} else {
when xs is {
[] -> []
[_x, ..rest] -> drop(rest, n - 1)
}
}
}
"#};
assert_fmt(src, expected)
}
#[test]
fn test_format_nested_when() {
let src = indoc! {r#"
fn foo() {
when a is {
None -> "foo"
Some(b) -> when b is {
None -> "foo"
Some(c) -> when c is {
None -> "foo"
Some(_) -> "foo"
}
}
}
}
"#};
let expected = indoc! {r#"
fn foo() {
when a is {
None -> "foo"
Some(b) ->
when b is {
None -> "foo"
Some(c) ->
when c is {
None -> "foo"
Some(_) -> "foo"
}
}
}
}
"#};
assert_fmt(src, expected)
}
#[test]
fn test_format_imports() {
let src = indoc! {r#"
use aiken/list
// foo
use aiken/bytearray
use aiken/transaction/certificate
// bar
use aiken/transaction
use aiken/transaction/value
"#};
let expected = indoc! {r#"
// foo
use aiken/bytearray
use aiken/list
// bar
use aiken/transaction
use aiken/transaction/certificate
use aiken/transaction/value
"#};
assert_fmt(src, expected)
}

View File

@ -1,2 +1,3 @@
mod format;
mod lexer; mod lexer;
mod parser; mod parser;