Interpretador puro não tem máquina virtual, porque se tiver ele é um compilador.
Algumas pessoas chamam de interpretador quando você roda direto do fonte, mas estritamente falando ele não é.
Nunca vi algo que especifique claramente que a VM precisa um bytecode (o nome correto do que você chama de código de máquina), mas eu entendo que deveria ser. Pode ter um mecanismo que tem um compilador e VM ligados e que rodam em conjunto, ou seja, pega o fonte, compila para o bytecode e então o roda.
Esse é um assunto que tem muita confusão pela internet, então tem que tomar cuidado. Esse é um dos casos que eu pretendo falar mais e tentar mostrar algo mais correto (na verdade o que eu já respondi aqui é uma boa introdução e servirá de base para algo mais completo).
O interpretador de verdade praticamente não existe mais. Não vi o que você fez, mas ele fica lendo e executando cada instrução? Por exemplo, se estiver em um laço, ele vai interpretar todas as vezes que passa por ele? Isso é a interpretação real. Se não faz isso, está fazendo uma compilação, porque está transformando o código fonte em algo (pode ser muita coisa) que aí é executado, isso não deixa de ser uma compilação, sob demanda, mas é. É o caso de JS, que em vez de gerar um bytecode para uma VM costuma gerar para o processador real da máquina. Se tem compilação não é interpretador, ainda que algumas pessoas o chamem assim.