g | x | w | all
Bytes Lang Time Link
200Haskell241105T081713ZDannyu N
053Charcoal210731T111556ZNeil
094Retina 0.8.2210731T104315ZNeil
060Jelly210724T201718ZNick Ken
071JavaScript210723T021602Ztsh
073Perl 5 p210723T034402ZAnders K

Haskell, 203 200 bytes

Saved 3 bytes thanks to Unrelated String.

import Text.ParserCombinators.ReadP
data T=L Char|T:!T
p=chainl1(between(char '(')(char ')')p<++(L<$>get))$pure(:!)
s(L c)=[c]
s(L c:!r)=c:s r
s(l:!r)='(':s l++')':s r
c t=do(r,"")<-readP_to_S p t;s r

Try it online!

I don't know how to golf this further, but practically speaking, this kind of parsing is really where parser combinators shine.

Charcoal, 53 bytes

F⁺(S¿⁼ι(⊞υ⟦⟧«F⁼ι)≔⪫⊟υωιF‹¹L§υ±¹⊞υ⟦⪫()⪫⊟υω⟧⊞§υ±¹ι»⪫⊟υω

Try it online! Link is to verbose version of code. Explanation:

F⁺(S

Loop over the input with an extra ( prefixed as this is golfier then setting up the stack manually.

¿⁼ι(⊞υ⟦⟧«

For each (, push a new empty list to the stack.

F⁼ι)

If the next character is a ), then...

≔⪫⊟υωι

the next term is actually the joined list at the top of the stack.

F‹¹L§υ±¹

If the list at the top of the stack already has two terms, then...

⊞υ⟦⪫()⪫⊟υω⟧

... replace it with a list of a () wrapped term.

⊞§υ±¹ι

Push the current term to the list at the top of the stack.

»⪫⊟υω

Join and output any remaining terms.

Retina 0.8.2, 94 bytes

+1`((\w|(\()|(?<-3>\)))+?(?(3)^)){2}(?!\))
($&)
+`\(((\w|(\()|(?<-3>\)))+)\)(?(3)^)(?=\)|$)
$1

Try it online! Link includes test cases. Explanation:

+1`

Make the first possible substitution each time, looping until no more substitutions are possible. This means that in the case of abcde, only ab is surrounded the first time, then (ab)c the second time, et cetera.

((\w|(\()|(?<-3>\)))+?(?(3)^)){2}(?!\))
($&)

Put parentheses around any pair of expressions that does not already have one.

+`\(((\w|(\()|(?<-3>\)))+)\)(?(3)^)(?=\)|$)
$1

Remove any parentheses not needed under right association.

Jelly, 60 bytes

;Ṫ$FL$¡1ĿØ(jƊ¹ŒḊ?€
=þØ(Ä_Ż}ỊṖʋ/aSƲœp⁸;2ĿW$}¥2/Ẏ3œṖW;¥2/ẎƲÐLÇ

Try it online!

A full program taking a string argument and printing the result to STDOUT. Uses recursion in both links, but in both cases replacing 1Ŀ or 2Ŀ with ß fails to work.

JavaScript, 71 bytes

Saved 2 bytes by Arnauld.

f=(e,l='',r=e.shift())=>(r=r>{}?r:r<f&&f(e))?f(e,l[1]?`(${l})`+r:l+r):l

Try it online!

Perl 5 -p, 73 bytes

1while$e='(\w|\((?1)*\))',s/$e$e(?=$e)/($&)/;1while s/\(($e*)\)(?!$e)/$1/

Try it online!