Інтерпретатор

Інтерпретатор мови програмування (interpreter) — програма чи технічні засоби, необхідні для виконання інших програм, вид транслятора, який здійснює пооператорну (покомандну, построкову) обробку, перетворення у машинний код та виконання програми або запиту (на відміну від компілятора, який транслює у машинні коди всю програму без її виконання).

Інтерпретатори можуть працювати як з початковим кодом програми (англ. source code), написаним мовою програмування, так і з байт-кодом (інтерпретатори байт-коду).

Типи інтерпретаторів

Простий інтерпретатор аналізує і відразу виконує (власне інтерпретація) програму покомандно (або порядково), по мірі надходження тексту програми на вхід інтерпретатора. Перевагою такого підходу є миттєва реакція. Недолік — такий інтерпретатор виявляє помилки в тексті програми тільки при спробі виконання команди (або рядка) з помилкою.

Інтерпретатор компілюючого типу — це система з компілятора, який перекладає текст програми в проміжне представлення, наприклад, в байт-код або p-код, і власне інтерпретатора, який виконує отриманий проміжний код (так звана віртуальна машина). Перевагою таких систем є більша швидкодія виконання програм (за рахунок винесення аналізу початкового коду в окремий, разовий прохід, і мінімізації цього аналізу в інтерпретаторі). Недоліки — більші вимоги до ресурсів і вимога на коректність тексту програми. Застосовується в таких мовах, як Java, Tcl, Perl (використовується байт-код), REXX (зберігається результат синтаксичного аналізу), а також у різних СУБД (використовується p-код).

Інтерпретатор компілюючого типу складається з компілятора мови і простого інтерпретатора з мінімізованим аналізом початкового коду; цей код у такому випадку не обов'язково повинен мати текстовий формат — це може бути машинний код якоїсь наявної апаратної платформи. Наприклад, віртуальні машини типу QEMU, Bochs, VMware містять у собі інтерпретатори машинного коду процесорів сімейства x86.

Деякі інтерпретатори (наприклад, для мов Lisp, Scheme, Python, Basic та інших) можуть працювати в режимі діалогу або так званого циклу читання-обчислення-друку (англ. read-eval-print loop, REPL). У такому режимі інтерпретатор зчитує закінчену конструкцію мови (наприклад, s-expression у мові Lisp), виконує її, друкує результати, після чого переходить до очікування введення користувачем наступної конструкції.

Унікальною є мова Forth, яка здатна працювати як в режимі інтерпретації, так і компіляції вхідних даних, дозволяючи переключатись між цими режимами в довільний момент, як під час трансляції початкового коду, так і під час роботи програм.[1]

Слід також зазначити, що режими інтерпретації можна знайти не тільки в програмному, а й апаратному забезпеченні. Так, багато мікропроцесорів інтерпретують машинний код за допомогою вбудованих мікропрограм, а процесори сімейства x86, починаючи з Pentium (наприклад, на архітектурі Intel P6), під час виконання машинного коду попередньо транслюють його у внутрішній формат (в послідовність мікрооперацій).

Порівняння інтерпретатора та компілятора

Програми, як правило, пишуть мовою високого рівня, яка повинна бути перетворена в машинний код для виконання центральним процесором. Це перетворення виконує компілятор або інтерпретатор.

Розробка програмного забезпечення

Під час розробки програмного забезпечення, програмісти роблять часті зміни у початковому коді. При використанні компіляторів, кожен раз після внесення змін у початковий код компілятор транслює змінені початкові файли і компонує всі файли бінарного коду разом, перш ніж програма може бути виконана. Чим більша програма, тим більшим є час очікування компілювання. З іншого боку, при використанні інтерпретатора, очікування набагато менше, бо інтерпретатору не треба транслювати всю програму, а просто потрібно транслювати код, що зараз виконується, на проміжне представлення (або не транслювати його взагалі), що вимагає набагато менше часу, щоб програма могла бути виконана.

Розповсюдження

Компілятор перетворює початковий код у бінарні інструкції для процесора певної архітектури, що робить його менш портативним. Такий переклад здійснюється тільки один раз в середовищі розробника, після чого той же бінарний файл можна розповсюдити на машини користувача, де він може бути виконаний без додаткового перекладу. Крос-компілятор може генерувати бінарний код для машини користувача, навіть якщо вона має інший процесор, ніж машина розробника, на якій відбувалась компіляція коду.

Програма, що інтерпретується, може поширюватися у вигляді початкового коду. Вона має бути трансльована на кожній машині, що займає більше часу, але робить розповсюдження програми незалежним від архітектури машини. Однак переносимість початкового коду, що інтерпретується, залежить від того, чи має цільова машина відповідний інтерпретатор. Якщо інтерпретатор треба розповсюджувати разом з початковим кодом, загальний процес встановлення програми ускладнюється, порівняно з постачанням одного виконуваного файлу. Те, що інтерпретований код легко читається і копіюється людьми, може представляти проблему з точки зору авторського права. Тим не менш, існують різноманітні системи шифрування і заплутування. Доставка проміжного коду, наприклад, байт-коду, має такий же ефект заплутування, але байт-код можна декодувати з допомогою декомпілятора або дизасемблера.

Ефективність

Основним недоліком інтерпретованих програм є те, що процес інтерпретації зазвичай набагато повільніший, ніж запуск скомпільованої програми. Різниця в швидкості може різнитися від незначної до достатньо відчутної: часто на порядок, а іноді й більше. Проте час інтерпретації програми може бути швидшим, ніж загальний час, необхідний для компіляції і запуску. Це особливо важливо, під час прототипування і тестування коду: цикл редагування-інтерпретація-налагодження часто може бути набагато коротший, ніж редагування-компіляція-запуск-налагодження.

Інтерпретація коду відбувається повільніше, ніж запуск скомпільованого коду, тому що інтерпретатор повинен аналізувати кожну інструкцію у програмі кожного разу, коли вона виконується, а потім виконувати потрібну дію, в той час як скомпільований код просто виконує фіксовані дії, визначені під час компіляції. Цей аналіз під час виконання відомий як «додаткові витрати інтерпретації». Доступ до змінних в інтерпретованих програм також повільніший, тому що операція зв'язування ідентифікаторів з місцями зберігання повторюється під час виконання, тоді як компілятором виконується один раз під час компіляції.

Існують різні компроміси між швидкістю розробки програмного забезпечення при використанні інтерпретатора і швидкістю виконання програми при використанні компілятора. Деякі системи (наприклад, Lisp) дозволяють інтерпретованому і скомпільованому коду викликати один одного і обмінюватися змінними. Це означає, що поточний код, який був протестований і налагоджений інтерпретатором, може бути скомпільований, і таким чином отримати більшу швидкість виконання, в той час як інший код розробляється. Багато інтерпретаторів не виконують оператори програми безпосередньо, а приводять його до більш компактної внутрішньої форми. Багато інтерпретаторів мови BASIC замінюють зарезервовані слова одним байтом токена, який може бути використаний для пошуку команди в таблиці переходів.

Проміжний код

Зазвичай початковий код мови програмування високого рівня компілюється у проміжну мову, яка буде скомпільована або інтерпретована у машинний код.

Проміжний код для Java

Початковий код на Java компілюється в проміжний код, який буде інтерпретовано.

Початковий код Проміжний код Інтерпретатор

Проміжною мовою для Java є байт-код, інтерпретатор — Java Virtual Machine (JVM). Файл байт-коду є універсальним, тоді як інтерпретатор є унікальним для кожної платформи.

Приклад коду

Розглянемо наступний приклад на мові Java.

  outer:
  for (int i = 2; i < 1000; i++) {
      for (int j = 2; j < i; j++) {
          if (i % j == 0)
              continue outer;
      }
      System.out.println (i);
  }

Компілятор Java може транслювати цей код в наступний байт-код:

  0:   iconst_2
  1:   istore_1
  2:   iload_1
  3:   sipush  1000
  6:   if_icmpge       44
  9:   iconst_2
  10:  istore_2
  11:  iload_2
  12:  iload_1
  13:  if_icmpge       31
  16:  iload_1
  17:  iload_2
  18:  irem
  19:  ifne    25
  22:  goto    38
  25:  iinc    2, 1
  28:  goto    11
  31:  getstatic       #84; //Field java/lang/System.out:Ljava/io/PrintStream;
  34:  iload_1
  35:  invokevirtual   #85; //Method java/io/PrintStream.println:(I)V
  38:  iinc    1, 1
  41:  goto    2
  44:  return

Проміжний код для мов .NET Framework

Процес компіляції програм для мов .NET Framework в CIL та інтерпретації за допомогою CLR

Мови, сумісні з платформою .NET Framework, такі як Visual Basic, C#, Visual F#, VB.NET, J#, компілюються в Common Intermediate Language (CIL) — проміжну мову для платформи .NET Framework. Отриманий проміжний код інтерпретується Common Language Runtime (CLR) — загальномовним середовищем виконання. Специфікація CIL та CLR є реалізацією специфікації Common Language Infrastructure (CLI) — специфікації загальномовної інфраструктури компанії Microsoft.

Код на CIL генерують всі компілятори для платформи .NET Framework. Мова CIL по структурі та мнемоніці нагадує мову асемблер. Проте CIL містить деякі високорівневі конструкції, і писати на CIL значно легше, ніж на асемблері.

Приклад коду

Приклад програми на C#:

static void Main(string[] args)
{
outer:
    for (int i = 2; i < 1000; i++)
    {
         for (int j = 2; j < i; j++)
         {
              if (i % j == 0)
                   goto outer;
         }
         Console.WriteLine(i);
     }
}

Вигляд цієї ж програми на CIL:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  .maxstack  2
  .locals init ([0] int32 i,
           [1] int32 j)
  IL_0000:  ldc.i4.2
            stloc.0
            br.s       IL_001f
  IL_0004:  ldc.i4.2
            stloc.1
            br.s       IL_0011
  IL_0008:  ldloc.0
            ldloc.1
            rem
            brfalse.s  IL_0000
            ldloc.1
            ldc.i4.1
            add
            stloc.1
  IL_0011:  ldloc.1
            ldloc.0
            blt.s      IL_0008
            ldloc.0
            call       void [mscorlib]System.Console::WriteLine(int32)
            ldloc.0
            ldc.i4.1
            add
            stloc.0
  IL_001f:  ldloc.0
            ldc.i4     0x3e8
            blt.s      IL_0004
            ret
}

Parrot

Parrot VM — це віртуальна машина, призначена для ефективної компіляції та виконання байт-коду для динамічних мов. В Parrot на даний момент реалізована підтримка багатьох мов, серед яких Tcl, Javascript, Ruby, Lua, Scheme, PHP, Python, Perl 6, APL і .NET транслятор байт-коду.

Віртуальна машина Parrot аналогічна віртуальним машинам Java і .NET платформи. Проте, на відміну від вказаних двох, які розроблені для статично типізованих мов як Java чи C#, Parrot розроблено для динамічно типізованих мов програмування.

Віртуальна машина Parrot написана на мові C. Саме тому, що Parrot призначена для підтримки різноманітних мов високого рівня, її архітектура доволі загальна та багатофункціональна.

Основні компоненти Parrot

Парсери PASM і PIR

Для компілювання початкового коду у PIR(Parrot Intermediate Representation) — проміжне представлення Parrot — доступно два парсери. IMCC використовується зараз, але є неефективним. PIRC є ефективнішим, але поки що нестабільний. Планується зробити PIRC основним парсером для PIR до виходу версії Parrot 1.0.

Компілятор байт-коду та оптимізатор

Компілятор байт-коду — складова Parrot, що відповідає за перетворення вхідних коду на PASM або PIR у байт-код Parrot. Цей байт-код виконується швидко і ефективно.

Іншим компонентом Parrot є оптимізатор байт-коду, який відповідає за низькорівневі оптимізації байт-коду Parrot.

Ітерпретатор

Тоді як компілятор байт-коду приймає вхідний код після обробки парсерами PIRC або IMCC і перетворює його в байт-код для зберігання і подальшого виконання, функцією інтерпретатора є безпосереднє виконання отриманого PIR та PASM коду. Це означає, що немає ніякого проміжного етапу компіляції, і скрипт можна виконати швидко, без необхідності компіляції.

Приклад коду

PIR

Приклад циклу:

.sub loopy
        .local int counter
        counter = 0
LOOP:   if counter > 10 goto DONE
        print counter
        print " "
        inc counter
        goto LOOP
DONE:
        print "counter\n"
        end
.end
PASM

Приклад циклу:

set     I1, 1
REDO:
  gt      I1, 10, END
  print   I1
  print   " "
  inc     I1
  branch  REDO
END:
  print "\n"
  end
Результат
0 1 2 3 4 5 6 7 8 9 10

Див. також

Джерела

  1. Jeff Fox. Chapter 2. More Interpretation. Thoughtful Programming and Forth (англ.). UltraTechnology. Архів оригіналу за 22 серпня 2011. Процитовано 12 травня 2013.