Replace fprettify with ffmt Fortran formatter#1334
Replace fprettify with ffmt Fortran formatter#1334sbryngelson wants to merge 3 commits intoMFlowCode:masterfrom
Conversation
56d5339 to
4bbff8d
Compare
📝 WalkthroughWalkthroughThis pull request applies code formatting standardization across the codebase. Changes include consolidating array dimension spacing from 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip You can customize the tone of the review comments and chat replies.Configure the |
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/simulation/m_bubbles_EL.fpp (1)
1557-1590:⚠️ Potential issue | 🟠 MajorSync device state before building the restart buffer.
This routine packs
gas_p,gas_mv,intfc_rad,intfc_vel,Rmax_stats, and related fields on the host, but those values are updated in GPU kernels elsewhere in the module. Without aGPU_UPDATE(host=...)here, non-unified-memory runs can write stale restart data.Based on learnings: Use GPU_UPDATE macro with FYPP eval directive ($:) to synchronize data between CPU and GPU with parameters: host (GPU to CPU), device (CPU to GPU).💡 Suggested fix
+ $:GPU_UPDATE(host='[lag_id, mtn_pos, mtn_posPrev, mtn_vel, intfc_rad, intfc_vel, bub_R0, Rmax_stats, Rmin_stats, & + & bub_dphidt, gas_p, gas_mv, gas_mg, gas_betaT, gas_betaC]') + if (bub_id > 0) then allocate (MPI_IO_DATA_lag_bubbles(max(1, bub_id), 1:lag_io_vars))src/common/m_mpi_common.fpp (1)
273-286:⚠️ Potential issue | 🔴 CriticalGuard the receive-side allocations to the root rank and provide a serial fallback.
MPI_GATHERpopulatesrecountsonly onroot. Non-root ranks then use uninitializedrecountsvalues to computedisplsand allocategathered_vector, causing undefined behavior. Additionally, the non-MPI build has no#elseclause, leaving theintent(out)gathered_vectorunallocated despite the subroutine's contract requiring it to be assigned.Suggested fix
`#ifdef` MFC_MPI - allocate (recounts(num_procs)) + allocate (recounts(num_procs), displs(num_procs)) + recounts = 0 + displs = 0 call MPI_GATHER(counts, 1, MPI_INTEGER, recounts, 1, MPI_INTEGER, root, MPI_COMM_WORLD, ierr) - - allocate (displs(size(recounts))) - - displs(1) = 0 - - do i = 2, size(recounts) - displs(i) = displs(i - 1) + recounts(i - 1) - end do - - allocate (gathered_vector(sum(recounts))) + if (proc_rank == root) then + do i = 2, size(recounts) + displs(i) = displs(i - 1) + recounts(i - 1) + end do + allocate (gathered_vector(sum(recounts))) + else + allocate (gathered_vector(0)) + end if call MPI_GATHERV(my_vector, counts, mpi_p, gathered_vector, recounts, displs, mpi_p, root, MPI_COMM_WORLD, ierr) +#else + allocate (gathered_vector(counts)) + gathered_vector = my_vector `#endif`
🧹 Nitpick comments (3)
src/common/m_compile_specific.f90 (1)
15-96: Please run full three-target tests for thissrc/commonchange.Even formatting-only updates here affect all executables, so run the complete matrix (
./mfc.sh test -j 8).Based on learnings: Changes to
src/common/affect all three executables (pre_process, simulation, post_process) and should be tested comprehensively.src/common/m_variables_conversion.fpp (1)
353-355: Follow through on the GPU TODOs before they become stale debt.The TODOs in Line 353 and Line 354 sit on a hot-path routine and are easy to lose during formatter-only PRs. Please either resolve or open a tracked issue and reference it inline.
If you want, I can draft the issue text (scope + acceptance criteria) for these two TODOs.
src/pre_process/m_check_patches.fpp (1)
529-531: Makes_check_model_geometryself-contained for patch-id formatting.This message currently depends on module-global
iStrbeing set by callers. Convertingpatch_idlocally in this subroutine removes hidden coupling.♻️ Proposed refactor
impure subroutine s_check_model_geometry(patch_id) integer, intent(in) :: patch_id + character(len=10) :: patch_id_str logical :: file_exists + call s_int_to_str(patch_id, patch_id_str) inquire (file=patch_icpp(patch_id)%model_filepath, exist=file_exists) @:PROHIBIT(.not. file_exists, & & "Model file " // trim(patch_icpp(patch_id)%model_filepath) // " requested by patch " // trim(iStr) & & // " does not exist")! Replace trim(iStr) with: trim(patch_id_str)
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c0861c00-8165-4045-9914-ad497a3bd230
📒 Files selected for processing (84)
.gitignoreffmt.tomlsrc/common/include/1dHardcodedIC.fppsrc/common/include/2dHardcodedIC.fppsrc/common/include/3dHardcodedIC.fppsrc/common/include/ExtrusionHardcodedIC.fppsrc/common/include/acc_macros.fppsrc/common/include/macros.fppsrc/common/include/omp_macros.fppsrc/common/m_boundary_common.fppsrc/common/m_checker_common.fppsrc/common/m_chemistry.fppsrc/common/m_compile_specific.f90src/common/m_constants.fppsrc/common/m_delay_file_access.f90src/common/m_derived_types.fppsrc/common/m_finite_differences.fppsrc/common/m_helper.fppsrc/common/m_helper_basic.fppsrc/common/m_model.fppsrc/common/m_mpi_common.fppsrc/common/m_nvtx.f90src/common/m_phase_change.fppsrc/common/m_precision_select.f90src/common/m_variables_conversion.fppsrc/post_process/m_checker.fppsrc/post_process/m_data_input.f90src/post_process/m_data_output.fppsrc/post_process/m_derived_variables.fppsrc/post_process/m_global_parameters.fppsrc/post_process/m_mpi_proxy.fppsrc/post_process/m_start_up.fppsrc/post_process/p_main.fppsrc/pre_process/m_assign_variables.fppsrc/pre_process/m_boundary_conditions.fppsrc/pre_process/m_check_ib_patches.fppsrc/pre_process/m_check_patches.fppsrc/pre_process/m_checker.fppsrc/pre_process/m_data_output.fppsrc/pre_process/m_global_parameters.fppsrc/pre_process/m_grid.f90src/pre_process/m_icpp_patches.fppsrc/pre_process/m_initial_condition.fppsrc/pre_process/m_mpi_proxy.fppsrc/pre_process/m_perturbation.fppsrc/pre_process/m_simplex_noise.fppsrc/pre_process/m_start_up.fppsrc/pre_process/p_main.f90src/simulation/include/inline_riemann.fppsrc/simulation/m_acoustic_src.fppsrc/simulation/m_body_forces.fppsrc/simulation/m_bubbles.fppsrc/simulation/m_bubbles_EE.fppsrc/simulation/m_bubbles_EL.fppsrc/simulation/m_bubbles_EL_kernels.fppsrc/simulation/m_cbc.fppsrc/simulation/m_checker.fppsrc/simulation/m_compute_cbc.fppsrc/simulation/m_compute_levelset.fppsrc/simulation/m_data_output.fppsrc/simulation/m_derived_variables.fppsrc/simulation/m_fftw.fppsrc/simulation/m_global_parameters.fppsrc/simulation/m_hyperelastic.fppsrc/simulation/m_hypoelastic.fppsrc/simulation/m_ib_patches.fppsrc/simulation/m_ibm.fppsrc/simulation/m_igr.fppsrc/simulation/m_mpi_proxy.fppsrc/simulation/m_muscl.fppsrc/simulation/m_pressure_relaxation.fppsrc/simulation/m_qbmm.fppsrc/simulation/m_rhs.fppsrc/simulation/m_riemann_solvers.fppsrc/simulation/m_sim_helpers.fppsrc/simulation/m_start_up.fppsrc/simulation/m_surface_tension.fppsrc/simulation/m_time_steppers.fppsrc/simulation/m_viscous.fppsrc/simulation/m_weno.fppsrc/simulation/p_main.fpptoolchain/bootstrap/format.shtoolchain/indenter.pytoolchain/pyproject.toml
| # MFC Fortran formatting configuration | ||
| # These are the defaults — this file makes them explicit. | ||
|
|
||
| indent-width = 4 |
There was a problem hiding this comment.
Use 2-space indentation in formatter config.
Line 4 sets indent-width = 4, which conflicts with the project Fortran style and can cause persistent formatting drift.
Suggested fix
-indent-width = 4
+indent-width = 2As per coding guidelines: Use 2-space indentation, lowercase keywords, and explicit intent on all subroutine/function arguments in Fortran code.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| indent-width = 4 | |
| indent-width = 2 |
| x_step = x_cc(1) - x_cc(0) | ||
| delta_x = merge(x_cc(0) - domain_xstart + x_step/2.0, & | ||
| x_cc(index_x) - domain_xstart + x_step/2.0, num_dims == 1) | ||
| delta_x = merge(x_cc(0) - domain_xstart + x_step/2.0, x_cc(index_x) - domain_xstart + x_step/2.0, num_dims == 1) |
There was a problem hiding this comment.
Use _wp-qualified literals in delta_x expression.
2.0 should be 2.0_wp to keep kind-consistent arithmetic with real(wp) operands.
Proposed fix
- delta_x = merge(x_cc(0) - domain_xstart + x_step/2.0, x_cc(index_x) - domain_xstart + x_step/2.0, num_dims == 1)
+ delta_x = merge(x_cc(0) - domain_xstart + x_step/2.0_wp, x_cc(index_x) - domain_xstart + x_step/2.0_wp, num_dims == 1)As per coding guidelines: “Use real(wp) for all computational variables; literal constants need the _wp suffix (e.g., 1.0_wp, 1e-6_wp).”
|
|
||
| #ifdef _WIN32 | ||
| call system('for /F %i in ("'//trim(dirpath)//'") do @echo %~ni > '//trim(tmpfilepath)) | ||
| call system('for /F %i in ("' // trim(dirpath) // '") do @echo %~ni > ' // trim(tmpfilepath)) |
There was a problem hiding this comment.
Shorten Line 85 to pass style checks.
Line 85 exceeds the 100-char limit (S001).
Suggested fix
- call system('for /F %i in ("' // trim(dirpath) // '") do `@echo` %~ni > ' // trim(tmpfilepath))
+ call system('for /F %i in ("' // trim(dirpath) // '") do `@echo` %~ni > ' // &
+ trim(tmpfilepath))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| call system('for /F %i in ("' // trim(dirpath) // '") do @echo %~ni > ' // trim(tmpfilepath)) | |
| call system('for /F %i in ("' // trim(dirpath) // '") do `@echo` %~ni > ' // & | |
| trim(tmpfilepath)) |
🧰 Tools
🪛 Fortitude (0.8.0)
[error] 85-85: line length of 101, exceeds maximum 100
(S001)
| impure subroutine s_mpi_allreduce_sum(var_loc, var_glb) | ||
|
|
||
| real(wp), intent(in) :: var_loc | ||
| real(wp), intent(in) :: var_loc | ||
| real(wp), intent(out) :: var_glb | ||
|
|
||
| #ifdef MFC_MPI | ||
| integer :: ierr !< Generic flag used to identify and report MPI errors | ||
|
|
||
| ! Performing the reduction procedure | ||
| call MPI_ALLREDUCE(var_loc, var_glb, 1, mpi_p, & | ||
| MPI_SUM, MPI_COMM_WORLD, ierr) | ||
| call MPI_ALLREDUCE(var_loc, var_glb, 1, mpi_p, MPI_SUM, MPI_COMM_WORLD, ierr) | ||
|
|
||
| #endif |
There was a problem hiding this comment.
Return the local value when MPI is disabled.
These wrappers leave var_glb undefined in non-MPI builds, unlike s_mpi_allreduce_integer_sum and s_mpi_allreduce_vectors_sum. The serial path should preserve the caller contract by copying var_loc.
💡 Suggested fix
impure subroutine s_mpi_allreduce_sum(var_loc, var_glb)
...
`#ifdef` MFC_MPI
call MPI_ALLREDUCE(var_loc, var_glb, 1, mpi_p, MPI_SUM, MPI_COMM_WORLD, ierr)
+#else
+ var_glb = var_loc
`#endif`
...
impure subroutine s_mpi_allreduce_min(var_loc, var_glb)
...
`#ifdef` MFC_MPI
call MPI_ALLREDUCE(var_loc, var_glb, 1, mpi_p, MPI_MIN, MPI_COMM_WORLD, ierr)
+#else
+ var_glb = var_loc
`#endif`
...
impure subroutine s_mpi_allreduce_max(var_loc, var_glb)
...
`#ifdef` MFC_MPI
call MPI_ALLREDUCE(var_loc, var_glb, 1, mpi_p, MPI_MAX, MPI_COMM_WORLD, ierr)
+#else
+ var_glb = var_loc
`#endif`Also applies to: 437-448, 457-468
| integer, private :: col(7) = [int(Z'0000ff00'), int(Z'000000ff'), int(Z'00ffff00'), int(Z'00ff00ff'), int(Z'0000ffff'), & | ||
| & int(Z'00ff0000'), int(Z'00ffffff')] |
There was a problem hiding this comment.
Wrap the col initializer to satisfy lint line-length limits.
Line 12 currently exceeds the configured maximum length (Fortitude S001).
🧹 Proposed fix
- integer, private :: col(7) = [int(Z'0000ff00'), int(Z'000000ff'), int(Z'00ffff00'), int(Z'00ff00ff'), int(Z'0000ffff'), &
- & int(Z'00ff0000'), int(Z'00ffffff')]
+ integer, private :: col(7) = [ &
+ int(Z'0000ff00'), int(Z'000000ff'), int(Z'00ffff00'), &
+ int(Z'00ff00ff'), int(Z'0000ffff'), int(Z'00ff0000'), &
+ int(Z'00ffffff') &
+ ]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| integer, private :: col(7) = [int(Z'0000ff00'), int(Z'000000ff'), int(Z'00ffff00'), int(Z'00ff00ff'), int(Z'0000ffff'), & | |
| & int(Z'00ff0000'), int(Z'00ffffff')] | |
| integer, private :: col(7) = [ & | |
| int(Z'0000ff00'), int(Z'000000ff'), int(Z'00ffff00'), & | |
| int(Z'00ff00ff'), int(Z'0000ffff'), int(Z'00ff0000'), & | |
| int(Z'00ffffff') & | |
| ] |
🧰 Tools
🪛 Fortitude (0.8.0)
[error] 12-12: line length of 125, exceeds maximum 100
(S001)
| q_prim_vf(i)%sf = q_prim_vf(i)%sf*(1._wp - q_prim_vf(alf_idx)%sf)/alf_sum%sf | ||
| end do |
There was a problem hiding this comment.
Use cell-indexed updates here; current assignments are array-wide.
In Line 310 (and repeated at Line 335, Line 375, Line 487, Line 609), q_prim_vf(i)%sf and alf_sum%sf are updated without (j,k,l). Inside this per-cell routine, that writes full fields instead of the current cell and can corrupt neighboring cells.
Proposed fix pattern (apply to each repeated block)
- alf_sum%sf = 0._wp
+ alf_sum%sf(j, k, l) = 0._wp
do i = adv_idx%beg, adv_idx%end - 1
- alf_sum%sf = alf_sum%sf + q_prim_vf(i)%sf
+ alf_sum%sf(j, k, l) = alf_sum%sf(j, k, l) + q_prim_vf(i)%sf(j, k, l)
end do
do i = adv_idx%beg, adv_idx%end - 1
- q_prim_vf(i)%sf = q_prim_vf(i)%sf*(1._wp - q_prim_vf(alf_idx)%sf)/alf_sum%sf
+ q_prim_vf(i)%sf(j, k, l) = q_prim_vf(i)%sf(j, k, l) * &
+ (1._wp - q_prim_vf(alf_idx)%sf(j, k, l)) / alf_sum%sf(j, k, l)
end doAlso applies to: 335-336, 375-376, 487-488, 609-610
| if (proc_rank == 0 .and. mod(cell_num, ncells/100) == 0) then | ||
| write (*, "(A, I3, A)", advance="no") char(13) // " * Generating grid: ", nint(100*real(cell_num)/ncells), "%" | ||
| end if |
There was a problem hiding this comment.
Guard progress modulus against zero stride.
ncells/100 can evaluate to zero on small meshes, making mod(cell_num, 0) invalid at runtime.
🛠️ Proposed fix
- if (proc_rank == 0 .and. mod(cell_num, ncells/100) == 0) then
+ if (proc_rank == 0 .and. mod(cell_num, max(1, ncells/100)) == 0) then
write (*, "(A, I3, A)", advance="no") char(13) // " * Generating grid: ", nint(100*real(cell_num)/ncells), "%"
end if📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (proc_rank == 0 .and. mod(cell_num, ncells/100) == 0) then | |
| write (*, "(A, I3, A)", advance="no") char(13) // " * Generating grid: ", nint(100*real(cell_num)/ncells), "%" | |
| end if | |
| if (proc_rank == 0 .and. mod(cell_num, max(1, ncells/100)) == 0) then | |
| write (*, "(A, I3, A)", advance="no") char(13) // " * Generating grid: ", nint(100*real(cell_num)/ncells), "%" | |
| end if |
|
|
||
| !> @brief This program takes care of setting up the initial condition and | ||
| !! grid data for the multicomponent flow code. | ||
| !> @brief This program takes care of setting up the initial condition and grid data for the multicomponent flow code. |
There was a problem hiding this comment.
Wrap Line 5 to satisfy lint line-length.
Line 5 is 130 chars and triggers Fortitude S001 (max 100), which can fail checks.
Suggested fix
-!> `@brief` This program takes care of setting up the initial condition and grid data for the multicomponent flow code.
+!> `@brief` This program takes care of setting up the initial condition and grid data
+!! for the multicomponent flow code.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| !> @brief This program takes care of setting up the initial condition and grid data for the multicomponent flow code. | |
| !> `@brief` This program takes care of setting up the initial condition and grid data | |
| !! for the multicomponent flow code. |
🧰 Tools
🪛 Fortitude (0.8.0)
[error] 5-5: line length of 130, exceeds maximum 100
(S001)
| real(wp) :: nR3bar | ||
| integer(wp) :: i, j, k, l | ||
| real(wp) :: nR3bar | ||
| integer(wp) :: i, j, k, l |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if integer(wp) was already present or introduced by the formatter
# Search for similar declarations in the codebase
rg -n "integer\(wp\)" --type-add 'fortran:*.{fpp,f90}' --type fortran | head -30Repository: MFlowCode/MFC
Length of output: 168
🏁 Script executed:
#!/bin/bash
# Check loop variable declarations in the same file
echo "=== Loop declarations in m_bubbles_EE.fpp ==="
rg -n "integer.*::" src/simulation/m_bubbles_EE.fpp | head -20
echo ""
echo "=== Typical loop index declarations across codebase ==="
rg -n "integer\s+::\s+[i|j|k|l]" --type-add 'fortran:*.{fpp,f90}' --type fortran | head -15
echo ""
echo "=== Check for wp definition in the module ==="
rg -n "parameter.*wp|wp\s*=" src/simulation/m_bubbles_EE.fpp | head -10Repository: MFlowCode/MFC
Length of output: 2039
Incorrect loop index type declaration at line 77.
Loop iterators i, j, k, l should be declared as integer, not integer(wp). The wp parameter represents floating-point working precision and should never qualify integer types. Correct to:
integer :: i, j, k, l
| ! Create ./D directory | ||
| write (path_D_dir, '(A,I0,A,I0)') trim(case_dir)//'/D' | ||
| write (path_D_dir, '(A,I0,A,I0)') trim(case_dir) // '/D' | ||
| call my_inquire(path_D_dir, file_exist) | ||
| if (.not. file_exist) call s_create_directory(trim(path_D_dir)) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
head -n 232 src/simulation/m_bubbles_EL.fpp | tail -n 15Repository: MFlowCode/MFC
Length of output: 642
🏁 Script executed:
rg -n "path_D_dir" src/simulation/m_bubbles_EL.fpp -B 2 -A 2Repository: MFlowCode/MFC
Length of output: 661
Replace the internal write with a direct assignment.
The format string '(A,I0,A,I0)' expects four items (A, I0, A, I0), but only one character expression is provided. This causes a runtime error on the fresh-start path when save_count == 0.
Suggested fix
- write (path_D_dir, '(A,I0,A,I0)') trim(case_dir) // '/D'
+ path_D_dir = trim(case_dir) // '/D'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ! Create ./D directory | |
| write (path_D_dir, '(A,I0,A,I0)') trim(case_dir)//'/D' | |
| write (path_D_dir, '(A,I0,A,I0)') trim(case_dir) // '/D' | |
| call my_inquire(path_D_dir, file_exist) | |
| if (.not. file_exist) call s_create_directory(trim(path_D_dir)) | |
| ! Create ./D directory | |
| path_D_dir = trim(case_dir) // '/D' | |
| call my_inquire(path_D_dir, file_exist) | |
| if (.not. file_exist) call s_create_directory(trim(path_D_dir)) |
d6a4128 to
c4f7661
Compare
Replace fprettify + indenter.py with ffmt (https://github.com/sbryngelson/ffmt), a fast, configurable Fortran formatter written in Rust. Integration: - toolchain/bootstrap/format.sh: single ffmt call replaces multi-pass fprettify + indenter.py loop - toolchain/pyproject.toml: replace fprettify dependency with ffmt - ffmt.toml: MFC formatting configuration - .gitignore: add .ffmt_cache/ - Remove toolchain/indenter.py Source formatting: - All .fpp/.f90 files reformatted with ffmt v0.2.0 - Unicode symbols replaced with LaTeX (sigma, pi, partial, etc.) - Operator spacing, keyword casing, declaration alignment - Named end statements, comment wrapping at 132 chars - Doxygen comment block re-wrapping Install: pip install ffmt Repo: https://github.com/sbryngelson/ffmt
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1334 +/- ##
==========================================
+ Coverage 45.01% 47.50% +2.49%
==========================================
Files 70 70
Lines 20562 19152 -1410
Branches 1962 1631 -331
==========================================
- Hits 9255 9098 -157
+ Misses 10179 9182 -997
+ Partials 1128 872 -256 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Replace fprettify + indenter.py with ffmt, a fast, configurable Fortran formatter written in Rust.
Why
What changes
Toolchain:
toolchain/bootstrap/format.sh— singleffmtcall replaces multi-pass fprettify + indenter.py looptoolchain/pyproject.toml—fprettify→ffmtffmt.toml— MFC formatting configurationtoolchain/indenter.pySource formatting (all .fpp/.f90 files):
enddo→end do)end subroutine→end subroutine s_foo)::alignment!>/!!supportσ→\sigma,∂→\partial)@:ALLOCATE (→@:ALLOCATE()!in comments, keyword-paren spacing (if(→if ()Install
Links
Test plan
./mfc.sh format -j 8works with ffmt./mfc.sh precheck -j 8passes./mfc.sh build -j 8compiles./mfc.sh test -j 8passesffmt --check src/returns 0)