Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .c3fmt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ max_line_length: 120

brace_style: ALLMAN
else_on_newline: true
newline_at_eof: false

spaces_before_trailing_comment: 1

align_assignments: true
align_comments: true
align_comments: true
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ You can look at [.c3fmt](.c3fmt) for the default configuration.
| `max_line_length` | Maximum line length before wrapping. | `120` |
| `brace_style` | The brace style to use: `ALLMAN` or `K&R`. | `ALLMAN` |
| `else_on_newline` | Whether to put `else` on a new line. | `true` |
| `newline_at_eof` | Ensure formatted output ends with a newline. | `false` |
| `spaces_before_trailing_comment` | The number of space inserted before trailing comments | `1` |
| `align_assignments` | Align `=` and `=>` in consecutive declarations/assignments. | `true` |
| `align_comments` | Align trailing comments in consecutive lines. | `true` |
| `align_string_concat` | Align wrapped adjacent string literals under the first literal's column (otherwise indent one level). | `false` |

## Building

Expand Down Expand Up @@ -149,4 +151,3 @@ The test suite includes:
## Known issues

*(none yet)*

10 changes: 9 additions & 1 deletion src/config.c3
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ struct Config

BraceStyle brace_style;
bool else_on_newline;
bool newline_at_eof;

uint spaces_before_trailing_comment;

bool align_assignments;
bool align_comments;
bool align_string_concat;
}

fn Config default_config()
Expand All @@ -34,9 +36,11 @@ fn Config default_config()
.max_line_length = 120,
.brace_style = ALLMAN,
.else_on_newline = true,
.newline_at_eof = false,
.spaces_before_trailing_comment = 1,
.align_assignments = true,
.align_comments = true,
.align_string_concat = false,
};
}

Expand Down Expand Up @@ -118,17 +122,21 @@ fn Config? parse_from_file(String file_path) => @pool()
}
case "else_on_newline":
conf.else_on_newline = val.to_bool()!;
case "newline_at_eof":
conf.newline_at_eof = val.to_bool()!;
case "spaces_before_trailing_comment":
conf.spaces_before_trailing_comment = (uint)val.to_int()!;
case "align_assignments":
conf.align_assignments = val.to_bool()!;
case "align_comments":
conf.align_comments = val.to_bool()!;
case "align_string_concat":
conf.align_string_concat = val.to_bool()!;

default:
return INVALID_CONFIG~;
}
}

return conf;
}
}
200 changes: 171 additions & 29 deletions src/formatter.c3
Original file line number Diff line number Diff line change
Expand Up @@ -449,27 +449,55 @@ fn bool C3Fmt.has_trailing_comma(&self, TSNode node)
return false;
}

fn bool is_alignable_block_node(TSNode node)
fn bool is_declaration_wrapper(TSNode node)
{
String type = node.type();
if (type == "declaration_stmt" || type == "global_declaration")
return type == "declaration_stmt" || type == "global_declaration";
}

fn TSNode declaration_target(TSNode node)
{
if (!is_declaration_wrapper(node)) return node;

for (uint i = 0; i < ts::node_named_child_count(node); ++i)
{
if (ts::node_named_child_count(node) == 0) return false;
node = ts::node_named_child(node, 0);
type = node.type();
TSNode child = ts::node_named_child(node, i);
if (child.type() == "doc_comment" || child.is_comment()) continue;
return child;
}
return type == "const_declaration" || type == "var_declaration" || (type == "declaration" && node.start_point_row() == node.end_point_row()) || type == "enum_constant";
return (TSNode){};
}

fn TSNode find_assignment_op(TSNode node)
fn void C3Fmt.process_declaration_prefix(&self, TSNode node, TSNode target)
{
TSNode target = node;
String type = node.type();
if (type == "declaration_stmt" || type == "global_declaration")
if (!is_declaration_wrapper(node)) return;

for (uint i = 0; i < ts::node_child_count(node); ++i)
{
if (ts::node_named_child_count(node) == 0) return (TSNode){};
target = ts::node_named_child(node, 0);
TSNode child = ts::node_child(node, i);
if (ts::node_eq(child, target)) return;
if (ts::node_is_named(child)) self.process_node(child);
}
}

fn bool is_alignable_block_node(TSNode node)
{
node = declaration_target(node);
if (ts::node_is_null(node)) return false;
String type = node.type();
if (type == "const_declaration" || type == "var_declaration" || type == "enum_constant") return true;
if (type != "declaration") return false;
if (node.start_point_row() == node.end_point_row()) return true;
if (!node.has_child_with_field("right")) return false;
String right_type = node.get_child_with_field("right").type();
return right_type == "binary_expr" || right_type == "ternary_expr" || right_type == "elvis_orelse_expr";
}

fn TSNode find_assignment_op(TSNode node)
{
TSNode target = declaration_target(node);
if (ts::node_is_null(target)) return (TSNode){};
String type = target.type();

for (uint i = 0; i < ts::node_child_count(target); ++i)
{
Expand Down Expand Up @@ -506,6 +534,17 @@ fn void C3Fmt.process_assignment_left(&self, TSNode target, TSNode op)
}
}

fn void C3Fmt.process_alignment_probe_node(&self, TSNode child)
{
if (child.type() == "declaration_stmt" || child.type() == "global_declaration")
{
self.process_composite(child, append_newline: false);
return;
}

self.process_node(child, false);
}

fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint end_index)
{
usz max_op_col = 0;
Expand All @@ -525,23 +564,15 @@ fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint
if (!ts::node_is_null(op))
{
// Format up to the '='
TSNode target = (child.type() == "declaration_stmt" || child.type() == "global_declaration") ? ts::node_named_child(child, 0) : child;
TSNode target = declaration_target(child);
self.process_assignment_left(target, op);
max_op_col = max(max_op_col, self.current_column());
}

// Reset and format whole node without trailing newline to find comment col
while (self.buf.len() > start_buf_len) (void)self.pop_token();

if (child.type() == "declaration_stmt" || child.type() == "global_declaration")
{
// We need to NOT append newline here
self.process_composite(child, append_newline: false);
}
else
{
self.process_node(child, false);
}
self.process_alignment_probe_node(child);

if (i + 1 < named_child_count)
{
Expand All @@ -557,6 +588,41 @@ fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint
}
}

if (self.config.max_line_length > 0 && max_op_col > 0)
{
for (uint i = start_index; i < end_index; ++i)
{
TSNode child = ts::node_named_child(parent, i);
if (child.is_comment()) continue;

sz start_buf_len = self.buf.len();
TSNode op = find_assignment_op(child);
if (ts::node_is_null(op)) continue;

TSNode target = declaration_target(child);
TSNode right = (target.type() == "macro_declaration" || !target.has_child_with_field("right"))
? (TSNode){}
: target.get_child_with_field("right");
if (ts::node_is_null(right) || right.type() != "call_expr") continue;

self.process_assignment_left(target, op);
usz op_col = self.current_column();
while (self.buf.len() > start_buf_len) (void)self.pop_token();

self.process_alignment_probe_node(child);
usz line_col = self.current_column();
while (self.buf.len() > start_buf_len) (void)self.pop_token();

if (max_op_col > op_col
&& line_col <= self.config.max_line_length
&& line_col + (max_op_col - op_col) > self.config.max_line_length)
{
max_op_col = 0;
break;
}
}
}

if (max_op_col > 80) {
max_op_col = 0;
}
Expand Down Expand Up @@ -587,9 +653,11 @@ fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint

if (!ts::node_is_null(op) && max_op_col > 0)
{
TSNode target = (child.type() == "declaration_stmt" || child.type() == "global_declaration") ? ts::node_named_child(child, 0) : child;
TSNode target = declaration_target(child);
TSNode right = (target.type() == "macro_declaration" || !target.has_child_with_field("right")) ? (TSNode){} : target.get_child_with_field("right");

self.process_declaration_prefix(child, target);

// Format until '='
self.process_assignment_left(target, op);

Expand All @@ -610,7 +678,7 @@ fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint
TSNode decl = ts::node_named_child(parent, m);
TSNode op2 = find_assignment_op(decl);
if (!ts::node_is_null(op2)) {
TSNode t2 = (decl.type() == "declaration_stmt" || decl.type() == "global_declaration") ? ts::node_named_child(decl, 0) : decl;
TSNode t2 = declaration_target(decl);
TSNode r2 = (t2.type() == "macro_declaration" || !t2.has_child_with_field("right")) ? (TSNode){} : t2.get_child_with_field("right");
// For macros, the 'right' side is the body, but it starts with =>
if (ts::node_is_null(r2) && t2.type() == "macro_declaration") r2 = op2;
Expand Down Expand Up @@ -648,9 +716,14 @@ fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint
}

// Semicolon of declaration_stmt if any
if (child.type() == "declaration_stmt" || child.type() == "global_declaration") {
for (uint s = 1; s < ts::node_child_count(child); ++s) {
if (is_declaration_wrapper(child)) {
bool found_target = false;
for (uint s = 0; s < ts::node_child_count(child); ++s) {
TSNode sc = ts::node_child(child, s);
if (!found_target) {
if (ts::node_eq(sc, target)) found_target = true;
continue;
}
if (ts::node_is_named(sc)) {
if (sc.is_comment()) {
self.buf.push(token::space());
Expand Down Expand Up @@ -899,6 +972,55 @@ fn void C3Fmt.process_composite(
}
}

<*
Format a sequence of adjacent string/bytes literals (implicit concatenation).
Each literal after the first becomes a wrap point, so an over-long concatenation
is broken with one literal per line, keeping the first on the opening line.
@param node : "The string_expr or bytes_expr node"
*>
fn void C3Fmt.process_concat_literals(&self, TSNode node)
{
// Comments between literals are rare; fall back to the plain layout for them.
for (uint i = 0; i < ts::node_child_count(node); ++i)
{
if (ts::node_child(node, i).is_comment())
{
self.process_composite(node);
return;
}
}

if (!self.wrapping_enabled)
{
self.process_composite(node);
return;
}

usz id = self.begin_wrap_group(2);

// When aligning, continuation literals line up under the first literal's
// column; otherwise they hang one indent level below.
bool align = self.config.align_string_concat;
usz align_col = align ? self.current_column() : 0;

for (uint i = 0; i < ts::node_child_count(node); ++i)
{
TSNode child = ts::node_child(node, i);

if (i > 0)
{
self.buf.space();
self.buf.wrap_point({ .indent = align ? 0 : 1, .group_id = id });
if (align)
{
self.buf.push(token::align(align_col));
}
}

self.process_node(child);
}
}

<*
Rawly format node sequentially, without appending any separator between them.
Useful for nodes like field accessor ("my_struct.my_field").
Expand Down Expand Up @@ -1777,6 +1899,8 @@ fn void C3Fmt.process_node(
}
}
bool is_multi = start_row != node.end_point().row;
usz id = 0;
bool has_const = false;
for (uint i = 0; i < ts::node_child_count(node); ++i)
{
TSNode child = ts::node_child(node, i);
Expand All @@ -1795,6 +1919,11 @@ fn void C3Fmt.process_node(
{
self.buf.newline();
self.indent();
id = self.begin_wrap_group(2);
}
else
{
id = self.begin_wrap_group(2);
}
continue;
}
Expand All @@ -1803,7 +1932,6 @@ fn void C3Fmt.process_node(
{
if (is_multi)
{
self.buf.newline();
self.dedent();
}
self.buf.text(";");
Expand All @@ -1822,6 +1950,18 @@ fn void C3Fmt.process_node(
}
}

if (type == "const_ident" && self.wrapping_enabled)
{
if (!is_multi || has_const)
{
self.buf.wrap_point({
.indent = is_multi ? 0 : 1,
.group_id = id
});
}
has_const = true;
}

if (child.is_comment())
{
String comment_str = self.extract_comment_string(child);
Expand Down Expand Up @@ -2460,8 +2600,10 @@ fn void C3Fmt.process_node(
case "doc_comment_contract_descriptor":
self.buf.text(node.get_text(self.source_code));

case "bytes_expr": // TODO idk what this is, a space separated list of bytes_literal ?
case "string_expr": // same...
case "bytes_expr": // space separated list of bytes_literal
case "string_expr": // adjacent string literals (implicit concatenation)
self.process_concat_literals(node);

case "lengthof":
case "$eval":
case "$stringify":
Expand Down
Loading