I've been accustomed to using integer return codes for many years, and after spending a lot of time at work using nothing but dynamically typed languages, I've grown impressed with how I can return very descriptive, human-readable error messages (sometimes even generated at run-time).
My basic problem is this: Take for example, my regular expression class. There are a billion places that parsing an expression can fail. I currently have one code to cover this: E_SYNTAX_ERROR. Not very descriptive, at all. This same code is returned if there's a misplaced parenthesis, the brackets don't agree, an unexpected end of input was encountered, the lower bound for a loop is higher than the upper bound, etc. Things get hairy if I have a giant enum that contains a billion little codes like Result::REGEX_INVALID_LOOP_CONSTRUCT, and I have to change the enum every time I add a new module. On the other hand, I could have a ton of enums, which neatly compartmentalizes the slightly less vague errors, but then I can't propagate them upward; if a file loading function encounters a line containing an invalid regex, and the regex parser throws an error, the file loading function returns an enum related to its functionality, which results in a loss of information since the regex-specific error code is lost.
This is, of course, ignoring niceties like giving the character offset where the problem was encountered, which a simple enum can't provide.
What other alternatives are there for this sort of thing? Are exceptions my only (undesirable) option, even if it isn't always an exceptional occurrence?