Предположим, что вы работаете с языком, скажем Lisp, хотя это не имеет значения. (Может быть C ++, Java, Ruby, что угодно.)
Хорошо, у вас есть реализация Lisp. Назовите эту реализацию Imp (только некоторые из придуманных сокращений для IMPlementation). Поскольку Imp сама по себе является программой, ваш компьютер может ее запустить. Теперь вы пишете свою собственную реализацию для Lisp, написанную на Lisp, и называете ее Circ. Circ - это просто программа, скомпилированная (или интерпретированная, если хотите) из кода на Лиспе. Ваш код написан так, что он читает в файл, анализирует его (обрабатывает его в значимые данные) и что-то делает с данными. Что это такое? В случае с Circ он выполняет данные.
Но как это получается?
Предположим, для простого случая, что код, который читает и анализирует Circ, представляет собой нечто простое, например, выполнение математических операций и вывод результата. Circ обрабатывает код в виде простых в использовании данных (хорошо для такого языка, как Lisp, с которого легко начать, но это не главное) и сохраняет его. Что ж, в Lisp вы можете написать код для сокращения чисел, поэтому код, написанный для Circ, может сделать то же самое, потому что он написан на Lisp. Таким образом, обработанные данные подключаются к некоторому коду дополнительной обработки ... и вуаля! У вас есть числовой результат! Затем ваша программа Circ выдаст результат.
То же самое можно сделать с более сложными вещами, чем простая математика. На самом деле вы можете компилировать / интерпретировать другие аспекты языка. Напишите достаточно этих «других аспектов» и склейте их вместе, вы получите компилятор для Lisp, написанный на Lisp.
Поскольку компилятор компилируется Imp, он может запускаться на вашей машине и presto! Вы сделали.