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

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до: навігація, пошук

Інтерпретатор мови програмування (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 компілюється в проміжний код, який буде інтерпретовано.

Text-x-java-source.svg
Application-x-executable.svg
Java.png
Сирцевий код Проміжний код Інтерпретатор

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

Text-x-java-source.svg

Application-x-executable.svg

Java.pngJava.pngJava.png

Windows icon.svgApple Logo.svgTux-shaded.svg

Приклад коду[ред.ред. код]

Розглянемо наступний приклад на мові 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 "\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 (en). UltraTechnology. Архів оригіналу за 2011-08-22. Процитовано 12 травня 2013.