I give each unit a FormationX# field and FormationZ# field. These specify the unit's formation position relative to their leader's position.
Then, FormationX and FormationZ is just calculated for each unit to line them up in rows and columns:


This is how I calculate FormationX and FormationZ:
Function AI_JoinSquad(Tank.Tank, Leader.Tank)
;Leave previous squad
AI_LeaveSquad(Tank)
;Set new leader
Tank\Leader = Leader
;Count the number of tanks in the last row
RowCount = 0
itm.TListItem = Tank\Leader\SquadList\LastItem
lastt.Tank = Null
While itm <> Null
t.Tank = Object.Tank(itm\Item)
If lastt <> Null Then
If t\FormationZ <> lastt\FormationZ Then Exit
End If
AveX# = AveX# + t\FormationX
If t\FormationX < LeastX# Then LeastX# = t\FormationX
If t\FormationX > MostX# Then MostX# = t\FormationX
If t\FormationZ < LeastZ# Then LeastZ# = t\FormationZ
RowCount = RowCount + 1
lastt.Tank = t
itm = itm\PrevItem
Wend
AveX# = AveX# / RowCount
;If the row is full (or there are no rows), create a new one
If RowCount > 5 Or RowCount = 0 Then
Tank\FormationZ = LeastZ - 15
Tank\FormationX = 0
Else
Tank\FormationZ = LeastZ
If AveX >= 0 Then
Tank\FormationX = LeastX - 15
Else
Tank\FormationX = MostX + 15
EndIf
EndIf
;Add tank to leader's squad list
Tank\SquadListItem = TList_AddItem(Tank\Leader\SquadList, Handle(Tank))
AI_SetTacticState(Tank, STATE_TACTIC_FOLLOW)
End Function
This method works well as new units are dynamically added to the formation, but not so good for filling in gaps when units are destrroyed.