Компилятор

Трасляция байт-кода в машинный код

Трансляция байт-кода в машинный код специальным транслятором байт-кода как указано выше неотъемлемая фаза динамической компиляции. Но трансляция байт-кода применима и для простого преобразования программы на байт-коде в эквивалентную программу на машинном языке. В машинный код может транслироваться как заранее скомпилированный байт-код. Но также трансляция байт-кода в машинный код может выполняться компилятором байт-кода сразу следом за компиляцией байт-кода. Практически всегда в последнем случае трансляция байт-кода выполняется внешним транслятором, вызываемым компилятором байт-кода.

Декомпиляция

Существуют программы, которые решают обратную задачу — перевод программы с низкоуровневого языка на высокоуровневый. Этот процесс называют декомпиляцией, а такие программы — декомпиляторами. Но поскольку компиляция — это процесс с потерями, точно восстановить исходный код, скажем, на C++, в общем случае невозможно. Более эффективно декомпилируются программы в байт-кодах — например, существует довольно надёжный декомпилятор для Flash. Разновидностью декомпиляции является дизассемблирование машинного кода в код на языке ассемблера, который почти всегда благополучно выполняется (при этом сложность может представлять самомодифицирующийся код или код, в котором собственно код и данные не разделены). Связано это с тем, что между кодами машинных команд и командами ассемблера имеется практически взаимно-однозначное соответствие.

Компилируемые и интерпретируемые языки программирования

Теги:

  • Языки программирования
  • Технологии

Желающие освоить язык программирования сталкиваются с такими понятиями, как компилятор и интерпретатор. Компиляция и интерпретация — это основа работы языков программирования.

Языки программирования в общем подходе делятся на два класса — компилируемые и интерпретируемые.  Стоит отметить, что эта классификация языков программирования на компилируемые и интерпретируемые, является весьма условной, поскольку для любого языка программирования может быть создан как компилятор, так и интерпретатор. Кроме того бывают языки программирования смешанного типа.

Мы полагаемся на такие инструменты, как компиляция и интерпретация, чтобы преобразовать наш код в форму, понятную компьютеру. Код может быть исполнен нативно, в операционной системе после конвертации в машинный (путём компиляции) или же исполняться построчно другой программой, которая делает это вместо ОС (интерпретатор).

Компилируемые языки

Программа на компилируемом языке при помощи специальной программы компилятора преобразуется (компилируется) в набор инструкций для данного типа процессора (машинный код) и далее записывается в исполняемый файл, который может быть запущен на выполнение как отдельная программа. Другими словами, компилятор переводит программу с языка высокого уровня на низкоуровневый язык, понятный процессору сразу и целиком, создавая при этом отдельную программу

Как правило, скомпилированные программы выполняются быстрее и не требуют для выполнения дополнительных программ, так как уже переведены на машинный язык. Вместе с тем при каждом изменении текста программы требуется ее перекомпиляция, что создает трудности при разработке. Кроме того, скомпилированная программа может выполняться только на том же типе компьютеров и, как правило, под той же операционной системой, на которую был рассчитан компилятор. Чтобы создать исполняемый файл для машины другого типа, требуется новая компиляция.

Компилируемые языки обычно позволяют получить более быструю и, возможно, более компактную программу, и поэтому применяются для создания часто используемых программ.

Примерами компилируемых языков являются Pascal, C, C++, Erlang, Haskell, Rust, Go, Ada.

Интерпретируемые языки

Если программа написана на интерпретируемом языке, то интерпретатор непосредственно выполняет (интерпретирует) ее текст без предварительного перевода. При этом программа остается на исходном языке и не может быть запущена без интерпретатора. Можно сказать, что процессор компьютера — это интерпретатор машинного кода. Кратко говоря, интерпретатор переводит на машинный язык прямо во время исполнения программы.

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

Примерами интерпретируемых языков являются PHP, Perl, Ruby, Python, JavaScript. К интерпретируемым языкам также можно отнести все скриптовые языки.

Многие языки в наши дни имеют как компилируемые, так и интерпретируемые реализации, сводя разницу между ними к минимуму. Некоторые языки, например, Java и C#, находятся между компилируемыми и интерпретируемыми. А именно, программа компилируется не в машинный язык, а в машинно-независимый код низкого уровня, байт-код. Далее байт-код выполняется виртуальной машиной. Для выполнения байт-кода обычно используется интерпретация, хотя отдельные его части для ускорения работы программы могут быть транслированы в машинный код непосредственно во время выполнения программы по технологии компиляции «на лету». Для Java байт-код исполняется виртуальной машиной Java (Java Virtual Machine, JVM), для C# — Common Language Runtime.

Перепечатка статьи допускается только при указании активной ссылки на сайт itmentor.by

Хочешь получать новые статьи первым? Вступай в сообщества ITmentor и

Оптимизация компиляторов

Оптимизация компилятора — это процесс улучшения качества объектного кода без изменения получаемых им результатов.

Разработчики первого компилятора FORTRAN стремились сгенерировать код, который был бы лучше, чем средний ручной ассемблер, чтобы клиенты действительно могли использовать их продукт. В одном из первых настоящих компиляторов им часто это удавалось.

Более поздние компиляторы, такие как компилятор IBM Fortran IV, уделяли больше внимания хорошей диагностике и более быстрому выполнению за счет оптимизации объектного кода. Только в серии IBM System / 360 IBM предоставила два отдельных компилятора: быстро исполняющийся модуль проверки кода и более медленный оптимизирующий.

Фрэнсис Э. Аллен , работая в одиночку и совместно с Джоном Коке , представила многие концепции оптимизации. В статье Аллена 1966 года « Оптимизация программ» было введено использование структур данных графа для кодирования содержимого программы для оптимизации. В ее статьях 1970 года « Анализ потока управления» и «Основы для оптимизации программ» установлены интервалы как контекст для эффективного и действенного анализа и оптимизации потока данных. В ее статье 1971 года «Кок», «Каталог оптимизирующих преобразований», впервые были описаны и систематизированы оптимизирующие преобразования. Ее статьи 1973 и 1974 годов по анализу межпроцедурных потоков данных распространили анализ на целые программы. В ее статье 1976 года с Коке описана одна из двух основных аналитических стратегий, используемых сегодня при оптимизации компиляторов.

Аллен разработала и реализовала свои методы в составе компиляторов для IBM 7030 Stretch — Harvest и экспериментальной системы Advanced Computing System . Эта работа позволила установить возможности и структуру современных машинно-независимых оптимизаторов. Затем она основала и возглавила проект PTRAN по автоматическому параллельному выполнению программ FORTRAN. Ее команда PTRAN разработала новые схемы обнаружения параллелизма и создала концепцию графа зависимости программы, основного метода структурирования, используемого большинством распараллеливающих компиляторов.

«Языки программирования и их компиляторы » Джона Кока и Джейкоба Шварца , опубликованные в начале 1970 года, посвятили алгоритмам оптимизации более 200 страниц. Он включал многие из уже знакомых нам техник, таких как устранение избыточного кода и снижение стойкости .

Оптимизация глазка

Оптимизация с помощью глазка — очень простой, но эффективный метод оптимизации. Он был изобретен Уильямом М. МакКиманом и опубликован в 1965 году в CACM. Он использовался в компиляторе XPL, который помог разработать МакКиман.

Оптимизатор COBOL Capex

Корпорация Capex разработала «Оптимизатор COBOL» в середине 1970-х годов для COBOL . В данном случае оптимизатор этого типа зависел от знания «слабых мест» стандартного компилятора IBM COBOL и фактически заменял (или исправлял ) участки объектного кода более эффективным кодом. Код замены может заменить линейный поиск в таблице с помощью бинарного поиска , например , или иногда просто заменить относительно «медленный» команду с известной быстрее той , которая была в противном случае функционально эквивалентны в его контексте. Эта техника теперь известна как « снижение силы ». Например, на оборудовании IBM System / 360 инструкция CLI была, в зависимости от конкретной модели, в два-пять раз быстрее, чем инструкция CLC для однобайтовых сравнений.

Современные компиляторы обычно предоставляют параметры оптимизации, поэтому программисты могут выбирать, выполнять ли проход оптимизации.

Бизнес и финансы

БанкиБогатство и благосостояниеКоррупция(Преступность)МаркетингМенеджментИнвестицииЦенные бумагиУправлениеОткрытые акционерные обществаПроектыДокументыЦенные бумаги — контрольЦенные бумаги — оценкиОблигацииДолгиВалютаНедвижимость(Аренда)ПрофессииРаботаТорговляУслугиФинансыСтрахованиеБюджетФинансовые услугиКредитыКомпанииГосударственные предприятияЭкономикаМакроэкономикаМикроэкономикаНалогиАудитМеталлургияНефтьСельское хозяйствоЭнергетикаАрхитектураИнтерьерПолы и перекрытияПроцесс строительстваСтроительные материалыТеплоизоляцияЭкстерьерОрганизация и управление производством

Виды компиляторов

  • Векторизующий. Базируется на трансляторе, транслирующем исходный код в машинный код компьютеров, оснащённых векторным процессором.
  • Гибкий. Сконструирован по модульному принципу, управляется таблицами и запрограммирован на языке высокого уровня или реализован с помощью компилятора компиляторов.
  • Диалоговый. См.: диалоговый транслятор.
  • Инкрементальный. Пересобирает программу, заново транслируя только измененные фрагменты программы без перетрансляции всей программы.
  • Интерпретирующий (пошаговый). Последовательно выполняет независимую компиляцию каждого отдельного оператора (команды) исходной программы.
  • Компилятор компиляторов. Транслятор, воспринимающий формальное описание языка программирования и генерирующий компилятор для этого языка.
  • Отладочный. Устраняет отдельные виды синтаксических ошибок.
  • Резидентный. Постоянно находится в оперативной памяти и доступен для повторного использования многими задачами.
  • Самокомпилируемый. Написан на том же языке программирования, с которого осуществляется трансляция.
  • Универсальный. Основан на формальном описании синтаксиса и семантики входного языка. Составными частями такого компилятора являются: ядро, синтаксический и семантический загрузчики.

Определение и история появления

Если говорить простыми словами, то компилятором является программа, преобразующая текст программы, написанной пользователем, в некоторую форму, которая пригодна для выполнения на вычислительной машине.

Появились такие программы вместе с зарождением первых языков программирования. Произошло это в конце 50-х годов. Получается, что история, которая связана с компиляторами и языками программирования насчитывает уже более шести десятков лет. Несмотря на такой серьезный срок, данное направление компьютерной науки ни в коем случае нельзя назвать устаревшим или устоявшимся. Наоборот, с ходом времени, с появлением новых отраслей и задач, для решения которых применяются компьютеры, возникает потребность в разработке новых, более удобных языков программирования. Соответственно, для этих языков нужны компиляторы. Windows, Linux, MacOS — для каждой платформы существуют свои разработки.

Генерация кода[ | код]

Генерация машинного кода | код

Большинство компиляторов переводит программу с некоторого высокоуровневого языка программирования в машинный код, который может быть непосредственно выполнен физическим процессором. Как правило, этот код также ориентирован на исполнение в среде конкретной операционной системы, поскольку использует предоставляемые ею возможности (системные вызовы, библиотеки функций). Архитектура (набор программно-аппаратных средств), для которой компилируется (собирается) машинно-ориентированная программа, называется целевой машиной.

Результат компиляции — исполнимый программный модуль — обладает максимально возможной производительностью, однако привязан к конкретной операционной системе (семейству или подсемейству ОС) и процессору (семейству процессоров) и не будет работать на других.

Для каждой целевой машины (IBM, Apple, Sun, Эльбрус и т. д.) и каждой операционной системы или семейства операционных систем, работающих на целевой машине, требуется написание своего компилятора. Существуют также так называемые кросс-компиляторы, позволяющие на одной машине и в среде одной ОС генерировать код, предназначенный для выполнения на другой целевой машине и/или в среде другой ОС. Кроме того, компиляторы могут оптимизировать код под разные модели из одного семейства процессоров (путём поддержки специфичных для этих моделей особенностей или расширений наборов команд). Например, код, скомпилированный под процессоры семейства Pentium, может учитывать особенности распараллеливания инструкций и использовать их специфичные расширения — MMX, SSE и т. п.

Некоторые компиляторы переводят программу с языка высокого уровня не прямо в машинный код, а на язык ассемблера. (Пример: PureBasic, транслирующий бейсик-код в ассемблер FASM.) Это делается для упрощения части компилятора, отвечающей за генерацию кода, и повышения его переносимости (задача окончательной генерации кода и привязки его к требуемой целевой платформе перекладывается на ассемблер), либо для возможности контроля и исправления результата компиляции (в том числе ручной оптимизации) программистом.

Генерация байт-кода | код

Результатом работы компилятора может быть программа на специально созданном низкоуровневом языке двоично-кодовых команд, выполняемых виртуальной машиной. Такой язык называется псевдокодом или байт-кодом. Как правило, он не есть машинный код какого-либо компьютера и программы на нём могут исполняться на различных архитектурах, где имеется соответствующая виртуальная машина, но в некоторых случаях создаются аппаратные платформы, напрямую выполняющие псевдокод какого-либо языка. Например, псевдокод языка Java называется байт-кодом Java и выполняется в Java Virtual Machine, для его прямого исполнения была создана спецификация процессора picoJava. Для платформы .NET Framework псевдокод называется Common Intermediate Language (CIL), а среда исполнения — Common Language Runtime (CLR).

Некоторые реализации интерпретируемых языков высокого уровня (например, Perl) используют байт-код для оптимизации исполнения: затратные этапы синтаксического анализа и преобразование текста программы в байт-код выполняются один раз при загрузке, затем соответствующий код может многократно использоваться без перекомляции.

Динамическая компиляция | код

Основная статья: Динамическая компиляция (англ.)

Из-за необходимости интерпретации байт-код выполняется значительно медленнее машинного кода сравнимой функциональности, однако он более переносим (не зависит от операционной системы и модели процессора). Чтобы ускорить выполнение байт-кода, используется динамическая компиляция, когда виртуальная машина транслирует псевдокод в машинный код непосредственно перед его первым исполнением (и при повторных обращениях к коду исполняется уже скомпилированный вариант).

Наиболее популярной разновидностью динамической компиляции является JIT. Другой разновидностью является инкрементальная компиляция.

CIL-код также компилируется в код целевой машины JIT-компилятором, а библиотеки .NET Framework компилируются заранее.

Компиляция — это…

Слово образовано от латинского compilatio, и буквально переводится как «ограбление или кража». Но в отрицательном контексте его можно встретить всё же намного реже, чем в положительном.

Но на самом деле, даже в таком виде она бывает полезной. Ведь если человек, создающий сие сочинение, хорошо разбирается в теме, и связывает чужие используемые отрывки своими рассуждениями и связками, с помощью которых легче отследить логическую цепочку

И это очень важно и полезно

Такие известные писатели, как С.Цвейг, А.Моруа, были отличными компиляторами, произведениями которых зачитывалось не одно поколение любителей литературы.

Справочная информация

ДокументыЗаконыИзвещенияУтверждения документовДоговораЗапросы предложенийТехнические заданияПланы развитияДокументоведениеАналитикаМероприятияКонкурсыИтогиАдминистрации городовПриказыКонтрактыВыполнение работПротоколы рассмотрения заявокАукционыПроектыПротоколыБюджетные организацииМуниципалитетыРайоныОбразованияПрограммыОтчетыпо упоминаниямДокументная базаЦенные бумагиПоложенияФинансовые документыПостановленияРубрикатор по темамФинансыгорода Российской Федерациирегионыпо точным датамРегламентыТерминыНаучная терминологияФинансоваяЭкономическаяВремяДаты2015 год2016 годДокументы в финансовой сферев инвестиционной

Clang-Tidy для автоматического рефакторинга кода

Tutorial

Существует много инструментов для анализа кода: они умеют искать ошибки, «узкие места», плохую архитектуру, предлагать оптимизацию. Но много ли среди них инструментов, которые могут не только найти, но и исправить код сами?

Представьте, что у вас есть большой проект на С или С++ (или даже С#), который разрабатывался много лет и многими людьми. В результате разные части проекта выглядят по-разному – нет единого стиля имен переменных, функций, типов данных. То есть в разных частях проекта использовался разный coding style: где-то имена в верхнем регистре, где-то CamelCase, где-то с префиксами, в других местах – без… Некрасиво, в общем.

Указание целевых входных и выходных параметров

Первым делом важно разобраться с тем, как указывать имя и тип создаваемой сборки (т.е., например, консольное приложение по имени MyShell.exe, библиотека кода
по имени MathLib.dll или приложение Windows Presentation Foundation по имени Halo8.ехе). Каждый из возможных вариантов имеет соответствующий флаг, который
нужно передать компилятору csc.ехе в виде параметра командной строки

Обратите внимание, что параметры, передаваемые компилятору командной строки (а также большинству других утилит командной строки), могут сопровождаться префиксом в виде символа дефиса (-) или слеша (/). Выходные параметры, которые может принимать компилятор C# приведены в следующей таблице:

Выходные параметры, которые может принимать компилятор C# приведены в следующей таблице:

Параметры компилятора csc.exe
Параметр Описание
/out Этот параметр применяется для указания имени создаваемой сборки. По умолчанию сборке присваивается то же имя, что у входного файла *.сs
/target:exe Этот параметр позволяет создавать исполняемое консольное приложение. Сборка такого типа генерируется по умолчанию, потому при
создании подобного приложения данный параметр можно опускать
/target:library Этот параметр позволяет создавать однофайловую сборку *.dll
/target:module Этот параметр позволяет создавать модуль. Модули являются элементами многофайловых сборок
/target:winexe Хотя приложения с графическим пользовательским интерфейсом
можно создавать с применением параметра /target: ехе, параметр /target: winexe позволяет предотвратить открытие окна консоли под остальными окнами

Чтобы скомпилировать TestApplication.cs в консольное приложение TestApplication.exe, перейдите в каталог, в котором был сохранен файл исходного кода (с помощью флага cd) и введите следующую команду:

Обратите внимание, что здесь C:\myProject — это путь к папке, в которой хранится файл TestApplication.cs. Так же обратите внимание, что здесь флаг /out не был указан явным образом, поэтому исполняемым файл получит
имя TestApplication.ехе из-за того, что именем входного файла является TestApplication

Кроме того,
для почти всех принимаемых компилятором C# флагов поддерживаются сокращенные
версии написания, наподобие /t вместо /target (полный список которых можно увидеть, введя в командной строке команду csc -?).

Теперь можно попробовать запустить приложение TestApplication.ехе из командной строки, введя имя его исполняемого файла:

Трансляция и компоновка

Следует отметить один важный исторический момент. На ранних стадиях своего развития, компилятор имел возможность выполнять не только трансляцию, но также и компоновку программы. Он состоял из двух составляющих, собственно транслятора и компоновщика. Это можно объяснить тем обстоятельством, что отделение компиляции от компоновки, как отдельного этапа, произошло гораздо позднее разработки компиляции. Но и сегодня во многих известных компиляторах есть физическое объединение с компоновщиками. По этой причине, терминология «компилятор» иногда заменяется словом «транслятор», которое является фактически синонимом.

Таблица переходов

Следующий пример ассемблерного кода часто можно встретить в больших смежных выражениях switch. Мы добавим case 4 и инструкцию по умолчанию.

Дизассемблированный вариант в radare2

1605793648917.png1605793656018.png

1 — инициализации переменной var_4h (i = 3) 2 — выполнения инструкций (add, printf)

Вот этот дизасcемблированный код довольно сложно быстро отличить от if и вообще понять что и как тут. В режиме графов всё будет более понятно.

Режим графов

1605793750977.png1605793673414.png1605793684884.png1605793691266.png

Режим графов — ваш друг в дизасcемблировании 🙂

На этом всё. Рекомендую попробовать самому написать программы на Си, скомпилировать и изучить дизасcемблированный код. Практика и ещё раз практика!

Спасибо за внимание. Не болейте

Виды компиляции[2]

  • Пакетная. Компиляция нескольких исходных модулей в одном задании.
  • Построчная. Машинный код порождается и затем исполняется для каждой завершённой грамматической конструкции языка. Внешне воспринимается как интерпретация, но устройство имеет иное.
  • Условная. Компиляция, при которой транслируемый текст зависит от условий, заданных в исходной программе директивами компилятора. (Яркий пример — работа препроцессора языка С и производных от него.) Так, в зависимости от значения некой константы некая заданная часть исходного текста программы транслируется или не транслируется.

Структурное построение компилятора

В процессе выполнения компиляции, реализуются следующие этапы:

  1. Анализ лексики. Выполняется преобразование символики исходной программы в набор лексем (абстрактных единиц).
  2. Анализ синтаксиса (грамматики). Выполняется преобразование набора лексем в дерево разбора.
  3. Анализ семантики. Выполняется обработка дерева разбора для определения его смысловых значений (семантики). Это может быть процесс привязки идентификаторов к их видам, определение совместимости т так далее. В итоге формируется промежуточное представление (код), и возможно формирование дополненного дерева разбора или даже нового дерева. Возможно так же формирование абстрактного набора команд, удобного для последующего применения.
  4. Повышение оптимальности. Удаляются лишние образования и формируется упрощённый код без смысловых потерь. Процесс оптимизации возможно осуществлять на различных этапах. К примеру, можно оптимизировать промежуточный или окончательный машинный код.
  5. Процесс генерации кода. Выполняется трансформация промежуточных представлений в коды конечного языка.

Замечание 1

Для разных типов компиляторов возможно подразделение или совмещение данных этапов в различных вариациях.

Примечания

  1. ГОСТ 19781-83 // Вычислительная техника. Терминология: Справочное пособие. Выпуск 1 / Рецензент канд. техн. наук Ю. П. Селиванов. — М.: Издательство стандартов, 1989. — 168 с. — 55 000 экз. — ISBN 5-7050-0155-X.; см. также ГОСТ 19781-90
  2. Першиков В. И., Савинков В. М. Толковый словарь по информатике / Рецензенты: канд. физ.-мат. наук А. С. Марков и д-р физ.-мат. наук И. В. Поттосин. — М.: Финансы и статистика, 1991. — 543 с. — 50 000 экз. — ISBN 5-279-00367-0.
  3. ↑ СТ ИСО 2382/7-77 // Вычислительная техника. Терминология. Указ. соч.
  4. Борковский А. Б. Англо-русский словарь по программированию и информатике (с толкованиями). — М.: Русский язык, 1990. — 335 с. — 50 050 (доп,) экз. — ISBN 5-200-01169-3.
  5. Толковый словарь по вычислительным системам = Dictionary of Computing / Под ред. В. Иллингуорта и др.: Пер. с англ. А. К. Белоцкого и др.; Под ред. Е. К. Масловского. — М.: Машиностроение, 1990. — 560 с. — 70 000 (доп,) экз. — ISBN 5-217-00617-X (СССР), ISBN 0-19-853913-4 (Великобритания).
  6. Н. А. Криницкий, Г. А. Миронов, Г. Д. Фролов. Программирование / Под ред. М. Р. Шура-Бура. — М.: Государственное издательство физико-математической литературы, 1963.

Виды компиляции[2]

  • Пакетная. Компиляция нескольких исходных модулей в одном задании.
  • Построчная. Машинный код порождается и затем исполняется для каждой завершённой грамматической конструкции языка. Внешне воспринимается как интерпретация, но устройство имеет иное.
  • Условная. Компиляция, при которой транслируемый текст зависит от условий, заданных в исходной программе директивами компилятора. (Яркий пример — работа препроцессора языка С и производных от него.) Так, в зависимости от значения некой константы некая заданная часть исходного текста программы транслируется или не транслируется.

Языки описания грамматики

Джон Бэкус предложил «металингвистические формулы» для описания синтаксиса нового языка программирования IAL, известного сегодня как АЛГОЛ 58 (1959). Работа Бэкуса была основана на канонической системе Поста, разработанной Эмилем Постом .

Дальнейшее развитие АЛГОЛА привело к АЛГОЛу 60 ; в своем отчете (1963) Питер Наур назвал нотацию Бэкуса нормальной формой Бэкуса (BNF) и упростил ее, чтобы минимизировать используемый набор символов. Однако Дональд Кнут утверждал, что BNF следует рассматривать скорее как форму Бэкуса – Наура , и это стало общепринятым использованием.

Никлаус Вирт определил расширенную форму Бэкуса – Наура (EBNF), усовершенствованную версию BNF, в начале 1970-х годов для PL / 0. Расширенная форма Бэкуса – Наура (ABNF) — еще один вариант. И EBNF, и ABNF широко используются для определения грамматики языков программирования, в качестве входных данных для генераторов синтаксического анализатора и в других областях, таких как определение протоколов связи.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector