Ну, я условно. Все, в любом случае, процессором выполняется. После того, как код выполняется процессором (генерируется строка), выхлоп этого выполнения (строка) подается на вход интерпретатора.
А почему в асм, а не сразу в двоичное представление?
понятно, что сразу в двоичное. Просто асм — взаимо-однозначная(во всяком случае теоретически) запись этого кода. Потому можно и так и так говорить.
На самом деле, в gcc AFAIK есть свой внутренний промежуточный язык, и код сначала в него преобразуется, а уж потом в бинарный код целевой платформы (их тоже больше одной). Теоретически gcc может и интерпретировать, а не только компилировать, чуть подправить нужно, и взлетит. Также и любой интерпретатор можно переделать в компилятор.
Да это все фигня, не надо зацикливаться на железе. Допустим, у нас есть строка текста «foo» на языке X. Предположим, что X - компилируемый язык. Транслятор оттранслирует строку в язык Y и запишет в файл. Потом ты можешь исполнить этот файл, и он возвратит куда-нибудь результат своего выполнения [smth]. Предположим теперь, что X - интерпретируемый язык. Транслятор оттранслирует строку в язык Y, затем исполнит получившееся выражение в [smth], и этот [smth] возвратится сразу на вход интерпретатора, в цикл чтения-выполнения-печати. При этом, разумеется, [smth] должен быть синтаксически верным выражением языка X.
Предположим теперь, что X - интерпретируемый язык. Транслятор оттранслирует строку в язык Y, затем исполнит получившееся выражение в [smth], и этот [smth] возвратится сразу на вход интерпретатора
не. выражение [smth] можно прямо так отдать исполнителю.
Так оно и отдается исполнителю сразу, мы же абстрагированы от реализации, когда его рассматриваем. Язык X может быть тем же языком, что и Y, это ничего не меняет по сути. Важно, что результат выполнения выражения возвращается обратно.
Теоретически gcc может и интерпретировать, а не только компилировать, чуть подправить нужно, и взлетит.
Не факт. Например для Си нельзя начинать выполнение, пока не оттранслирован весь файл. Поэтому классический интерпретатор «читаем команду, выполняем, читаем следующую» невозможен.
Также и любой интерпретатор можно переделать в компилятор.
Попробуй сделать компилятор picolisp, в котором eval должен получать весь контекст выполнения (потому как вместо макросов fexpr'ы).
Не факт. Например для Си нельзя начинать выполнение, пока не оттранслирован весь файл. Поэтому классический интерпретатор «читаем команду, выполняем, читаем следующую» невозможен.
1. очевидно ты путаешь с C++.
2. какая разница, что файл надо читать целиком? Это тут вообще не при чём, как оно файл читает роли не играет.
компилировалось в ассемблерный код и работало как в picolisp. Можешь хотя бы просто привести эквивалентный ассемблерный код (ну или C-подобный, если ассемблер не знаешь)
Намекну: (eval ...) в picolisp имеет доступ к полному контексту в исходном коде, аналогично #include в C
Входной текст «df > ~/test». Что является выходным текстом?
Машкод.
Транслятор — переводчик, переводит текст программы с одного языка (у тебя sh) на другой (машкод).
Машкода, эквивалентного алгоритму «прочитать свободное место и записатьв файл ~/test» sh не создаёт. Он при чтении входного текста сразу выполняет поиск файла, перенаправление вывода и запуск в новом процессе команды df. И, опять же, машкод для алгоритма «поиск файла, перенаправление вывода и запуск в новом процессе команды df» не создаётся. В sh вообще нет генерации машкода.
компилировалось в ассемблерный код и работало как в picolisp. Можешь хотя бы просто привести эквивалентный ассемблерный код (ну или C-подобный, если ассемблер не знаешь)
мне лениво. Как-то процессор ведь выполняет этот код? Значит это возможно, да? А если код есть, то почему его нельзя сохранить? Что тебе мешает?
Намекну: (eval ...) в picolisp имеет доступ к полному контексту в исходном коде, аналогично #include в C
выдели в куче памяти,и запиши туда свой контекст. Не вижу проблемы.
Ну тогда все программы, получающие ввод по твоему определению являются трансляторами.
«транслятором» является sh, команды получают уже готовый массив с разобранными параметрами. Например rm -rf /* получит массив с именами всех каталогов в корне.
Компилятор компилирует код в некое промежуточное состояние, которое потом выполняется процессором, интерпретируемый код выполняется интерпретатором, и то и то трансляторы
ЮМашкода, эквивалентного алгоритму «прочитать свободное место и записатьв файл ~/test» sh не создаёт.
этот код создавать не нужно, он уже создан, и записан в файле /bin/df, оболочка его просто запускает.
В sh вообще нет генерации машкода.
есть. Там есть сам маш код. Если ты написал (( X = 2+2 )), то будет создан машкод для вычисления 2+2. (скорее всего этот код уже там есть, и записан где-то в switch для всех арифм. операторов).
А дальше считаем этот массив с именами входной программой на языке «rm»
да. Дальше rm работает как транслятор, и транслирует этот массив во множество системных вызовов unlink(2). А их уже транслирует ядро, а точнее модуль ФС в машкод.
Во время выполнения кода модифицируется исходный код программы командой (eval (read)). Потом выполняется то, что получилось.
Всё равно, что в С можно было бы в качестве параметра #include подставлять значение переменной, полученное от пользователя. Тогда тоже компилятор был бы невозможен. Например, не компилировалось бы такое.
Во время выполнения кода модифицируется исходный код программы командой
это возможно в любом Тьюринг-полном ЯП, ассемблер не исключение.
Всё равно, что в С можно было бы в качестве параметра #include подставлять значение переменной
ты можешь выделить в куче массив, байты в котором будут иметь смысл «команды». Этот массив ты можешь изменять. И выполнять тоже можешь, читая байты специальной функцией execute().
И не трогай препроцессор, он вообще говоря — отдельная сущность. Это что-то вроде предварительной обработки исходного текста, которая делает из двух строк /bin/true Over9000 строчек, которые и компилируются.
Т.е. препроцессор к предмету обсуждения не относится.
Т.е. препроцессор к предмету обсуждения не относится
Относится. Я утверждаю, что нельзя сделать интерпретатор для языка, где eval имеет мощность не меньше, чем у препроцессора.
В этом случае после ввода пользователя мы получаем другую программу. Для интерпретатора это не препятствие (какая разница, откуда получать следующую команду — от пользователя или из файла), а для компилятора появляется необходимость перекомпилировать новый исходный код, но во время выполнения исходного кода уже нет и компилятора тоже нет, есть только скомпилированная программа.
а для компилятора появляется необходимость перекомпилировать новый исходный код
зачем? А если в программе есть цикл, который выполняется uint16_t раз, то ты тоже предлагаешь для каждого из 65536и чисел компилировать свой код? Нет, ты сделаешь один цикл.
Открой K&R, там ЕМНИП был пример калькулятора, который вычисляет любые выражения. Без всяких перекомпиляций. Хоть 2+2, хоть (1+4-(8*(4/5)))%6.
Т.ч. eval не нужен. Ну и он вовсе не обязан eval'ить именно тот ЯП, на котором он написан. Вот например eval в sed/perl eval'ит bash'ем. Пример с rm -rf на ЛОРе есть.