My question is the same as the title. I just want to know if there are any other translation techniques to get the intermediate code that doesn't rely on embedding actions into the parser (that is, the parser will strictly create the abstract syntax tree, it won't generate any code). Thanks for any answers.
If you have a parser, then the parser has to do something more than just "recognize" the input stream as a valid instance of the language. If you want the compiler to produce anything, some kind of actions must be attached to activities in which langauge fragments are matched. In some sense, it can't be anything but "syntax directed"; at the parsing stage, all you have is syntax.
Fundamentally, the parsing actions have to build a representation of the program that is "more compilable". I only know of a few basic methods:
These are all in the abstract really pretty much the same, in that the parser-generated representation contains linked structures whose elements have some direct semantic intepretation. These bits of structure with implied semantics are what the rest of the compiler keys off.
Now, the text is a representation of the program. You can avoid the "parsing" process (sort of) completely if you decide to implement you compiler as a Post system, a set of rewriting rules over strings. These are of the form of "if you see this string, replace it by this other string". Post system are provabaly Turing capable, so technically you can transform your source code into a string representing your target program with a sufficiently clever set of string rewriting rules. Nobody I know builds real compilers this way; I'm sure if you dig hard enough, you can find an obscure technical paper that does this.
One could reasonably argue that matching strings, as required by the Post system, is a kind of parsing (e.g., recognizing interesting structure), putting you back at the original question.
It is interesting to note that program transformation systems (I build one of these commercially) typically use a compiler front end to build ASTs, and then apply AST tree-to-tree rewrites to achieve thier purpose. The reason they are constructed this way is because they are really Post systems in diguise; any AST for your program can be trivally converted into a string (e.g., S-expressions), and the tree-rewrites can be converted into equivalent string rewrites. So a tree-rewriting system is just a Post system, but that makes it very powerful. Of course, one can combine this capability with other more traditional compiler methods, which is what we do with our product. That makes it more convenient; you don't have do everything as Post system.