Разработчик: ЛДО
ПРЕТРАНСЛЯТОР SubJava
При разработке расчетных программ на JAVA (в частности при работе с
комплексными числами) сильно страдает наглядность программного кода. Это
усложняет процесс разработки и сопровождения программ.
Существенное отличие программного кода от привычного математического языка
вызвано отсутствием в JAVA функций и операций между объектами.
SubJava - претранслятор, который позволяет использовать следующие возможности:
Наличие этих возможностей позволяет приблизить текст программы к привычному
математическому языку или языку другой прикладной области.
Претранслятор преобразует исходный .subjava файл в стандартный .java файл,
который может быть откомпилирован любым стандартным JAVA-компилятором.
При преобразовании проверяется синтаксис в объеме необходимом для выполнения
преобразования (т.е. ошибки, не влияющие на преобразование, он пропускает).
Претранслятор заменяет перекрываемые операции, функции и глобальные переменные
конструкциями, допустимыми в JAVA (т.е. выполняет подстановки аналогично
макроопределениям). Аналогично претранслятор выполняет неявные преобразования
типов данных при инициализации значений переменных, в вызовах методов или
функций (если эти преобразования были описаны).
Исходный текст подобен JAVA текстам, но может содержать операции между
объектами, функции, глобальные переменные (константы) и операторы substitutes
для указания претранслятору файлов объявлений подстановок допустимых операций
между объектами, функций, глобальных переменных и неявных преобразований
типов данных.
Операторы substitutions располагаются после операторов import (для каждого файла объявлений подстановок - свой оператор substitutions). Формат оператора:
substitutions <имя файла объявлений>;
Имя файла объявлений подстановок - это java имя (подобно имени класса в операторе import), поэтому оно может быть полным или частичным (с учетом предыдущих операторов import). Если отсутствует файл объявлений, полное имя которого совпадает с заданным именем, то он ищется в пакетах, объявленных в операторах import ( т.е. выполняется попытка найти файл, полное имя которого состоит из имени пакета, объявленного в import, и заданного имени ). Пример:
substitutions forDouble; substitutions mySubstitutions.forDouble;
Файлы объявлений подстановок реализуются как текстовые файлы с расширением
substitutions.
Объявления подстановок (далее объявления) внутри файла идут последовательно
в любом порядке.
Объявление может находиться в одной или более строках. В одной строке может
быть более одного объявления. Допускается использование любого числа разделительных
пробелов.
Файлы объявлений могут содержать комментарии аналогичные комментариям в JAVA-программах
(исключение: если комментарий находится в поле шаблона, то он войдет в
шаблон, а значит будет вставлен в текст .java файла при применении этой
подстановки).
При определении полного имени типа данных (тип операнда или типы аргументов) претранслятор учитывает операторы import, находящиеся в исходном тексте.
Это позволяет сделать объявление более кратким (вместо java.math.BigInteger
можно BigInteger).
В настоящее время набор допустимых операторов и их приоритет и ассоциативность
определяются стандартом JAVA.
Синтаксис шаблонов объявлений проверяется. В случае ошибки выдается диагностика.
Допустимы следующие типы объявлений (приводятся тип объявления, формат
и один или два примера):
prefixOperator : <знак операции><тип операнда> : <шаблон> :] prefixOperator : ++double : ++{0} :
postfixOperator : <тип операнда><знак операции> : <шаблон> : postfixOperator : java.lang.Double++ : new Double({0}.doubleValue()++ ) :
binaryOperator : <тип операнда><знак операции><тип операнда> : <шаблон> : binaryOperator : java.lang.Double+java.lang.Double :{0}.doubleValue()+{1}.doubleValue() :
friendOperator : <тип операнда><знак операции>><тип операнда> : <шаблон> : friendOperator : java.lang.Double+double : {0}.doubleValue()+{1} :
function : <имя функции>(<типы аргументов>) : <шаблон имени метода>( ... ) : function : SIN( double ) : Math.sin( ... ) : function : FUN( double, int ) : MyFunction.fun( ... ) :
function : <имя функции>(<типы аргументов>) : <шаблон> : function : SIN( java.lang.Double ) : Math.sin( {0}.doubleValue() ) : function : FUN( double, int ) : MyFunction.fun( {1}, {0} ) :
name : <глобальное имя> : <шаблон> : name : PI : Math.PI :
convertor : <тип данных, из которого преобразовывать> : <шаблон> : convertor : double : (int){0} : // double -> int convertor : java.lang.Double : {0}.intValue() : // Double -> int
Подстановки операторов используются при определении типа результата
операции в выражении: требуемая операция с заданными типами операндов (типом операнда для одноместной операции) ищется в списке подстановок операторов
и, если не нашли, то тип операции определяется по правилам неявного преобразования
в JAVA. Если тип результата операции не удалось определить - выдается сообщение.
Подстановки функций применяются при определении типа возвращаемого значения
при вызове метода в выражении: метод с требуемым именем и списком типов
параметров ищется в списке методов класса, в списке методов родительского
класса, и если не нашли, то в списке подстановок функций; определяются
все удовлетворяющие методы (а среди подстановок функции) и затем среди
найденных, используя алгоритм самого точного совпадения, удаляются лишние;
если осталось более одного метода (подстановки функции), то выдается
сообщение об ошибке. При поиске удовлетворительных методов и функций
используются подстановки неявных преобразователей типа.
Подстановки глобальных переменных и констант применяются при определении
типа переменной в выражении: требуемое имя ищется в списке переменных блока
и метода, в списке полей класса, родительского класса и интерфейсов, в
списке подстановок глобальных переменных и констант до первого результата
(т.е. как только нашли дальнейший поиск прекратили). Если не удалось
определить тип переменной - выдается сообщение.
Подстановки неявных преобразователей типа данных используются при инициализации
переменных в операторах объявления типа или при преобразовании формальных
параметров в фактические: в случае несовпадения типов (инициализируемого
и инициализирующего) ищется преобразователь. Если не удалось найти преобразователя
- выдается сообщение.
На работу претранслятора требуется время, соизмеримое со временем, необходимым
для компилятора javac, т.к. проверяется синтаксис (не полностью!), проверяется
наличие всех методов, переменных и полей (при определении их типов данных),
но основное время занимает загрузка необходимых классов (как классов претранслятора,
так и классов, используемых в транслируемом файле).
Хотя в большинстве случаев это несущественно, но необходимо отметить, что
все классы, на которые есть ссылки в транслируемом файле, должны быть откомпилированы
до запуска претранслятора (в отличие от компилятора javac претранслятор
не проверяет дату создания байт-кода классов и интерфейсов).
Претранслятор реализован на JAVA 1.1 (нельзя использовать с JAVA 1.0).
Претранслятор отлаживался в Windows95.
Претранслятор позволяет писать программный код на JAVA используя язык прикладной области (например приближая программный код к привычному математическому языку). Он позволяет эмулировать
так как если бы они уже были введены в JAVA без изменений JAVA компилятора
или JAVA машины (JVM) (т.е. без изменения спецификации JAVA).
Данная реализация поддерживает все возможности JAVA 1.1:
Претранслятор написан на JAVA, что делает его независимым от программно-аппаратной
среды.
Претранслятор вызывается из командной строки, что позволяет использовать
его в существующих средах разработки программ (IDE).
Реализована многоязыковая поддержка, что позволяет перевести диагностику
на язык пользователя.
В программировании давно обсуждается вопрос "где должны находиться
методы, реализующие перекрытия операций". В данной реализации сняты
все ограничения на местоположение реализующих методов. Это позволяет вводить
операции между объектами, не внося изменения в их классы (например, создавая
новый класс, содержащий необходимые методы, или как в примере 1 для java.lang.Double
описывать все в файле объявлений подстановок).
Данный претранслятор может быть использован при переводе программ из С++
в JAVA. Для этого надо создать файлы объявлений используемых функций, операций
между объектами, глобальных переменных и констант, необходимых неявных
преобразований типа данных. После чего формировать JAVA программу из текстов
программ на С++ и затем обработать полученный текст претранслятором.
При преобразовании структура исходного .subjava файла (отступы, индексы
соответствующих строк) по возможности сохраняются в java файле. Это упрощает
анализ ошибок, которые обнаружит компилятор JAVA при трансляции .java файла.
В настоящее время претранслятор SubJava реализован как набор java пакетов. Поэтому для установки достаточно развернуть архив (subjava.zip) от одного из корней, указанных в classpath. Т.е. файл SubJava.class должен находиться в каталоге, соответствующем java пакету subjava.* .
Вызов из командной строки:Можно использовать jar-архив subjava.jar (в этом случае нет необходимости распаковывать архив).
Вызов из командной строки :Параметры могут следовать в любой последовательности.
<имя файла> - имя файла с обязательным расширением subjava. Обрабатываемый файл должен находиться в текущей директории.
java subjava.SubJava a1.subjava
java subjava.SubJava -errors 10 a2.subjava
java subjava.SubJava -compiler "javac {0}.java" a3.subjava
java subjava.SubJava -output err a4.subjava
java -jar d:\Java\subjava.jar a1.subjava
java -jar c:\Java\subjava.jar -errors 10 a2.subjava
java -jar c:\Java\subjava.jar -compiler "javac {0}.java" a3.subjava
java -jar d:\Java\subjava.jar -output err a4.subjava
Противники перекрытия операций утверждают, что использование перекрытий операций приводит к неоднозначности толкования исходного кода, что противоречит идеологии Java. И в некоторых случаях они правы. Введение перекрытий операций в форме претранслятора к Java позволяет устранить этот недостаток (код в *.java, в который будет преобразован код из *.subjava будет иметь однозначное толкование). С другой стороны, реализация в форме транслятора в байт-код потребует разработки и собственной IDE, что достаточно трудоемко. А при реализации в форме претранслятора остается возможность использования любых существующих IDE и компиляторов Java.
2. Можно ли задавать приоритет и ассоциативность для перекрываемой операции?Авторы не видят необходимости введения такой возможности, т.к. на их взгляд это действительно снижает понимаемость выражений. В настоящее время приоритет и ассоциативность операции определяется стандартом языка Java. Введение этой возможности в принципе осуществимо.
3. Почему претранслятор не имеет аналогов template и enum из С++?Это первая реализация претранслятора. При следующей реализации эти потребности будут учтены. Хотя в настоящее время разработка этого проекта приостановлена в связи с отсутствием финансирования, поэтому когда это будет, пока неизвестно.
Данный пример демонстрирует введение операций для существующего класса
java.lang.Double.
Файл forDouble.substitutions:
convertor : Double : {0}.doubleValue() : // Double -> double convertor : double : new Double( {0} ) : // double -> Double binaryOperator : Double + Double : new Double( {0}.doubleValue() + {1}.doubleValue() ) : friendOperator : double + Double : new Double( {0} + {1}.doubleValue() ) : binaryOperator : Double * Double : new Double( {0}.doubleValue() * {1}.doubleValue() ) : friendOperator : double * Double : new Double( {0} * {1}.doubleValue() ) : binaryOperator : Double - Double : new Double( {0}.doubleValue() - {1}.doubleValue() ) : binaryOperator : double - Double : new Double( {0} - {1}.doubleValue() ) : binaryOperator : Double - double : new Double( {0}.doubleValue() - {1} ) : binaryOperator : Double / Double : new Double( {0}.doubleValue() / {1}.doubleValue() ) : binaryOperator : double / Double : new Double( {0} / {1}.doubleValue() ) : binaryOperator : Double / double : new Double( {0}.doubleValue() / {1} ) : binaryOperator : double += Double : {0} += {1}.doubleValue() :
Файл Test.subjava:
substitutions forDouble; public class Test { public static void main( String[] args ) { Double d1 = 1, d2 = 5; Double d3 = d1 + d2; d1 = 5 - d2; double d = d3; d += ( d * d1 ) / 7; } }
Полученный файл Test.java:
public class Test { public static void main( String[] args ) { Double d1 = new Double( 1 ), d2 = new Double( 5 ); Double d3 = new Double( d1.doubleValue() + d2.doubleValue() ); d1 = new Double( 5 - d2.doubleValue() ); double d = d3.doubleValue(); d += new Double( new Double( d * d1.doubleValue() ).doubleValue() / 7 ).doubleValue(); } }
Данный пример демонстрирует введение операций для класса Complex. Аналогично
можно ввести функции и операции между любыми объектами.
Файл forComplex.substitutions:
// substitutions for Complex convertor : double : new Complex( {0}) : // double -> Complex binaryOperator : Complex + Complex : {0}.add( {1} ) : friendOperator : double + Complex : {1}.add( {0} ) : binaryOperator : Complex * Complex : {0}.mul( {1} ) : friendOperator : double * Complex : {1}.mul( {0} ) : binaryOperator : Complex - Complex : {0}.sub( {1} ) : binaryOperator : double - Complex : new Complex( {0} - {1}.real(), -{1}.image() ) : binaryOperator : Complex - double : {0}.sub( {1} ) : binaryOperator : Complex / Complex : {0}.div( {1} ) : binaryOperator : double / Complex : new Complex( {0} ).div( {1} ) : binaryOperator : Complex / double : {0}.div( {1} ) : name : IM : Complex.I : function : sin( Complex ) : {0}.sin() : function : cos( Complex ) : Complex.cos( {0} ) :
Файл TestForComplex.subjava:
substitutions forComplex; public class TestForComplex { static Complex im = IM; Complex p1 = sin( im ); Complex p2 = cos( im ); public static void main( String[] args ) { Complex c1 = 1, c2 = -5; Complex c3 = new Complex( 1, 1 ); Complex c4 = c1 * c2 / c3; Complex c5 = 7 / 8; Complex c6 = 7 * c2 / 8; Complex c7 = sin( c1 ) * cos( c2 ) / c3; } }
Полученный файл TestForComplex.java:
public class TestForComplex { static Complex im = Complex.I; Complex p1 = im.sin(); Complex p2 = Complex.cos( im ); public static void main( String[] args ) { Complex c1 = new Complex( 1), c2 = new Complex( -5); Complex c3 = new Complex( 1, 1 ); Complex c4 = c1.mul( c2 ).div( c3 ); Complex c5 = new Complex( 7 / 8); Complex c6 = c2.mul( 7 ).div( 8 ); Complex c7 = c1.sin().mul( Complex.cos( c2 ) ).div( c3 ); } }
Данный пример демонстрирует использование стандартных математических функций.
Файл forMath.substitutions:
// substitutions for Math name : PI : Math.PI : function : sin( double ) : Math.sin( ... ) : function : cos( double ) : Math.cos( ... ) :
Файл TestForMath.subjava:
// example TestForMath substitutions forMath; public class TestForMath { static double pi = PI; public static void main( String[] args ) { double p1 = sin( pi ); double p2 = cos( 2*pi ); } }
Полученный файл TestForMath.java:
// example TestForMath public class TestForMath { static double pi = Math.PI; public static void main( String[] args ) { double p1 = Math.sin( pi ); double p2 = Math.cos( 2*pi ); } }
Претранслятор адаптирован к JDK 1.2.2.
Устранена небольшая неточность в обработке CLASSPATH.
Введена обработка локальных и анонимных классов.
Более корректная работа с path.