Perhaps something like this could work:
DATA: BEGIN OF w_bom,
matnr LIKE mast-matnr,
werks LIKE mast-werks,
stlnr LIKE mast-stlnr,
items LIKE STANDARD TABLE OF stpo,
END OF w_bom.
DATA: t_bom LIKE STANDARD TABLE OF w_bom.
PARAMETERS: p_matnr LIKE mast-matnr,
p_werks LIKE mast-werks.
PERFORM get_bom USING p_matnr p_werks.
FORM get_bom USING p1_matnr
p1_werks.
DATA: l_mast TYPE mast.
DATA: l_stpo TYPE stpo.
* Check if BOM material was loaded
READ TABLE t_bom TRANSPORTING NO FIELDS WITH KEY matnr = p1_matnr
werks = p1_werks.
CHECK sy-subrc <> 0.
* Check no raw material
CHECK p1_matnr(1) <> 'M' AND p1_matnr <> 'S'.
* Check BOM
SELECT * INTO l_mast
FROM mast
WHERE matnr = p1_matnr
AND werks = p1_werks.
ENDSELECT.
CHECK sy-subrc = 0.
CLEAR w_bom.
FREE w_bom-items.
*
w_bom-matnr = l_mast-matnr.
w_bom-werks = l_mast-werks.
w_bom-stlnr = l_mast-stlnr.
SELECT * INTO TABLE w_bom-items
FROM stpo
WHERE stlty = 'M'
AND stlnr = l_mast-stlnr.
CHECK sy-subrc = 0.
APPEND w_bom TO t_bom.
* Check next level
LOOP AT w_bom-items INTO l_stpo.
PERFORM get_bom USING l_stpo-idnrk l_mast-werks.
ENDLOOP.
ENDFORM.
The routine GET_BOM should call itself until the conditions to stop the search are satisfied, I can't try this code but you can try it or something like that.