Charm — это язык программирования, разработанный в начале 1990-х годов, схожий с языками RTL/2 , Pascal и C, а также содержащий некоторые собственные уникальные особенности. Язык Charm определяется контекстно-свободной грамматикой , поддающейся обработке рекурсивным спусковым парсером , как описано в основополагающих книгах по проектированию компиляторов . [1] [2]
Набор инструментов Charm, включающий компилятор, ассемблер и компоновщик, был доступен для платформы RISC OS компании Acorn . [3] Charm, переработанный для платформ RISC OS, впоследствии был рассмотрен в журнале Archive. [4]
Более подробно Charm описан в электронной книге «Программирование в Charm на Raspberry Pi» . [5]
Определение грамматики Charm в форме Бэкуса-Наура вместе с описательными примерами конструкций Charm дано на странице языка Charm. [6]
Язык имеет блочную структуру, при этом каждый блок начинается с ключевого слова языка, которое описывает операцию, выполняемую в блоке, например for , while , repeat ( iteration ), case , if ( selection ). Каждый блок заключен в разделители и . Кроме того, строки языка внутри блока обычно имеют отступ для ясности, хотя это не обязательно, поскольку пробелы игнорируются.{
}
Каждый грамматически соответствующий текст представляет собой набор исполняемого кода и связанных с ним данных, которые могут использоваться набором инструментов Charm в качестве компонента при сборке программы, которая может быть запущена в операционной системе, применяя предоставляемые ею сервисы для выполнения полезной работы, такой как обработка данных или взаимодействие с пользователями через графический пользовательский интерфейс (GUI).
Charm — это строго типизированный язык, но допускает некоторые неявные преобразования между числовыми и типами с плавающей точкой. Поддерживаются следующие основные типы переменных:
Агрегаты данных одного типа могут быть объявлены и статически инициализированы с использованием ключевого слова array , и они могут быть многомерными. Агрегаты разных типов могут быть объявлены с использованием ключевого слова record , и допустимо, чтобы такое объявление определяло объединение полей записи, которые перекрывают друг друга с точки зрения распределения памяти. Модули также могут агрегировать смесь статических и динамических членов данных. Экземпляры как записей, так и модулей (только динамическое содержимое) могут быть созданы в стеке или в куче с помощью оператора new . Модули также могут определять процедуру конструктора ~new для инициализации динамических данных и соответствующую процедуру деконструктора ~delete для освобождения ресурсов аналогично языку C++.
Данные или процедуры в рамках модуля могут быть сделаны глобальными для конечного приложения с помощью ключевого слова export . Если модуль хочет сослаться на процедуру или данные из другого модуля Charm, он делает это с помощью ключевого слова import . Модули могут содержать переменные-члены на основе экземпляров, которые доступны через процедуры, объявленные с ключевым словом dynamic через неявный первый параметр this pointer.
Ссылки на конструкции и процедуры данных могут быть сделаны с помощью ключевого слова ref . Их можно разыменовать с помощью ключевого слова val . При использовании ссылочных переменных доступны операторы сравнения для проверки того, ссылаются ли две ссылочные переменные на один и тот же элемент данных ( ) или являются ли данные, на которые они указывают, одними и теми же ( ). :=:
=
Оригинальная классическая программа Hello world, написанная на Charm, выглядит следующим образом:
ext proc write_string (ссылка на массив char); модуль привет; ent proc start(); write_string ("Привет, мир"); конец_процедуры; конец_модуля;
и эквивалентная последняя версия после эволюционных синтаксических изменений языка:
импортировать lib.Out; модуль Привет { экспортировать процедуру ~start () { Out.vdu.str ("Привет, мир").nl(); } }
Ожидается, что реализации набора инструментов предоставят компилятор и ассемблер для генерации объектных файлов из исходного кода Charm и исходного кода ассемблера, которые затем можно будет связать вместе с файлами библиотеки и поддержки времени выполнения для генерации исполняемой программы.
На момент написания статьи доступна (бесплатно) только одна установка набора инструментов Charm для загрузки. Сами инструменты написаны на языке Charm, а исходный код доступен на условиях GNU General Public License . Они работают на ПК с ОС RISC и платформах с процессорами ARM (таких как Raspberry Pi ), а также на эмуляторах для ОС RISC, размещенных на платформах Windows или Linux (таких как RPCEmu ). Генерация кода для операций с плавающей точкой двойной точности с аппаратной поддержкой поддерживается для платформ на базе чипов ARM, которые поддерживают архитектуру сопроцессора VFP версии 2.
Компилятор Charm — это рекурсивный однопроходный компилятор спуска, который анализирует исходный код Charm для генерации четверок вида result := lhs op rhs на промежуточном языке, поддерживающем арифметические, логические и потоковые операции управления. Данные хранятся во временных файлах, которые назначаются регистрам и ячейкам памяти в бэкэнде компилятора. В настоящее время существуют два бэкэнда, один из которых генерирует язык ассемблера Motorola 68000 , а другой — архитектуру ARM . [7]
Четверной вывод из примера «Hello World» выглядит так:
параметр l1$ вызов write_string[proc (ссылка на массив char) void]
и вывод ассемблера:
строка "привет" xdef _start align _start xref _write_string stmfd sp ! ,{ rp } adr r0 , _l1$ bl _write_string ldmfd sp ! ,{ pc } адрес align _l1$ строка "Привет, мир" прямой конец
В более поздних версиях Charm I/Oпроцедуры были разделены на собственные модули In и Out . Другие стандартные библиотечные процедуры организованы в набор записей со ссылками на процедуры в качестве полей. В рамках этой реорганизации write_string
метод теперь вызывается через модуль библиотеки времени выполнения Out
через статическую ссылку на член, .vdu
как процедура str
, т.е. в приведенном выше примере hello world write_string ("Hello world")
становится Out.vdu.str ("Hello world")
.
Ассемблер принимает мнемонику инструкций, объявления данных и директивы и создает объектный файл, содержащий информацию, легко понятную центральному процессору целевого процессора, в частности инструкции кода, закодированные в двоичном формате.
Один и только один из модулей Charm, связанных для формирования исполняемой программы, должен содержать процедуру, соответствующую одной из сигнатур:
экспортировать процедуру ~start () экспортировать процедуру ~start (int argc, ref array ref array char argv)
Это аналог функции main в языках C и Java . Здесь argc
содержится количество параметров, переданных в командной строке, и argv
ссылка на массив argc + 1
строк (одна строка на позиционный параметр по порядку и завершающий nil
).
Кроме того, модули могут опционально содержать статические процедуры запуска и завершения работы, вызываемые во время запуска и завершения работы программы, которые соответствуют сигнатурам:
экспортировать процедуру ~startup() экспортировать процедуру ~shutdown()
Компоновщик добавляет всю необходимую информацию заголовка, требуемую операционной системой для выполнения программы, и обеспечивает запуск кода поддержки ассемблера библиотеки времени выполнения, который настраивает среду выполнения (указатели данных и стека) и передает управление процедуре запуска приложения.
При необходимости создается файл карты, показывающий имена всех модулей, связанных для формирования программы, а также глобальные данные и ссылки на код, которые могут использоваться отладчиками и профилировщиками.