From Wikipedia:

A source-to-source translator, source-to-source compiler (S2S compiler), transcompiler, or transpiler is a type of translator that takes the source code of a program written in a programming language as its input and produces an equivalent source code in the same or a different programming language. A source-to-source translator converts between programming languages that operate at approximately the same level of abstraction.

An example is when you translate code with JS-preprocessors like TypeScript to JavaScript.

Contrast that with compiling to VM bytecode or LLVM IR, (from what I understand) when the compiler will compile the code to some basic bytecode, then, a virtual machine program will run the bytecode, and execute the program. A (huge) example is Java.

So what are the pros and cons of using one instead of another?

This question is first asked on the Definition phase. I got permission to use this here.


2 Answers 2


Transpiling is an excellent way to bootstrap a compiler, because you get to reuse an existing compiler.

There is more than one way to transpile. The way we bootstrapped the Mercury compiler was to write Prolog code to transpile a subset of Mercury to two closely-related languages: Prolog for execution, and Gödel just to use its type checker. We then rewrote that code in the same subset of Mercury (which, being very like Prolog syntactically, wasn't much work at all), at which point we could start writing the static analysis phases to get Gödel out of the development loop.

One under-appreciated "pro" is that you essentially get a foreign function interface for free, so this can save you having to write a standard library early on. The original reference implementation for C++, called Cfront, was a transpiler to C that was also compatible with C. This C compatibility is arguably one of the reasons why C++ succeeded as well as it did.

There are two very large "cons":

  1. Users of your compiler need another language implementation installed.
  2. It often messes up the user experience for the rest of the software toolchain, such as profiling and debugging.

Neither of these are a problem for TypeScript-to-JavaScript, because everyone has a JS implementation already, and tools (e.g. Chrome developer tools) are aware of transpiled languages. Programmers can't avoid knowing that there is JS underneath TS, but the experience isn't as painful as it could be.

  • $\begingroup$ Another major "con" is that one will be stuck with corner-case behavioral quirks of the target language. Of course, the same issue applies when targeting LLVM. $\endgroup$
    – supercat
    Commented Jul 13, 2023 at 18:18

The main benefit of targeting a higher-level language is that it can be much easier to compile from a high-level language to another one. The more language constructs and features that are shared by the source and target language, the less work it is to translate between them. For example, when compiling a garbage-collected language to another garbage-collected language, there is no need to implement a garbage collector yourself; you get it for free. Likewise for things like dynamic dispatch or exceptions.

Another significant benefit is that when implementing a compiler, we need a strong understanding of the target language. If the target language is one that you have experience writing in, then you will be in a better position compared to having to learn LLVM IR, which is both extraordinarily complicated and not something you would have prior experience with from your own projects.

One downside of targeting a high-level language is that if there is a mismatch between the semantics of the source language and the target language, then the gap may be harder to bridge than just implementing straight into a low-level target language. For example, if your language's control-flow is difficult to express without jumps (consider e.g. labelled break and continue when targeting a language like Python which doesn't have these), then you may have to insert flag variables or apply other program transformations to achieve the correct results, when the same control-flow would be straightforward if you were targeting a bytecode or assembly language which allowed jumps.

Another downside, particularly when targeting a high-level compiled language, is the compiler's performance. If your ultimate goal is to compile to machine code, but you get there via a C++ or Rust compiler which uses LLVM IR itself, then your compiler is going to be slower than one which outputs LLVM IR directly.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .