An interpreter is generally understood to execute a program without compiling the program in a separate step. However, most modern interpreters do not interpret the source code directly – this isn't really possible anyway for anything more complex than BASIC. Instead, they first parse the code, then produce an intermediate representation, and finally execute this representation.
Common intermediate representations are:
bytecode, which is a high-level cousin of machine code. They are executed on a virtual machine that uses this bytecode. The bytecode is not generally human-readable, although a human-readable serialization may exist (e.g. Jasmin assembler for JVM bytecode).
A language implementation may choose bytecode because it's essentially similar to most existing architectures, albeit more restricted, and thus safer. It is also really simple to store bytecode in a file, or interchange it otherwise. Most VMs execute bytecode (starting with the original P-Code, over CPython to the JVM).
other opcodes, e.g. in-memory data structures like ASTs. These sometimes do not have any permanent serialization.
A language implementation may choose to represent the code as a data structure because this is a lot more flexible (just as Lisp is more flexible than C), and because it removes the need to decode bytecodes inside the VM – making it simpler to implement. This route is most useful for simple projects, but is also used in some serious interpreters like
In practice, the boundary between compilers and interpreters has been blurred beyond recognition, and most interpreters could also be considered compilers because they compile to some intermediate representation (even if it isn't stored anywhere). The two useful distinguishing characteristics are:
- when the source is compiled – ahead of time, or before each execution
- what the compilation target is – machine code, some intermediate representation executed by a VM
Some implementations like the primary Java implementations defy overly simple categorization: Here, code is compiled ahead of time to a bytecode which is interpreted on a VM which may also perform JIT compilation to machine code.