Привет, Хабр! Хочу поделиться опытом разработки такой системы. Определяющими параметрами проблемно‑ориентированной системы являются:
Предметная область и характерные структуры данных
Набор методов/функций обработки и манипулирования данными
средства создания функций и написания скриптов, управляющих ходом исполнения
Предметная область - обработка и визуализация матриц и их последовательностей( стэков ).
Методы - матричные/векторные операции, линейная алгебра, регрессии, нахождение собственных значений и векторов, SVD-разложение ...
Система (набор консольных приложений) и библиотека реализованы на JAVA. В качестве скриптов управления первоначально использовались batch-файлы. В последних версиях для этой цели предлагается использовать jj-препроцессор - "java-JAVA" макро-процессор (см.ниже).
Приведен краткий обзор создания консольных приложений и описание jj-препроцессора.
Формат данных: ###-формат
Единицей хранения данных считается файл, который может содержать: матрицу, стэк( последовательность матриц/"кадров"), несколько стэков. Чтобы матрица была прочитана системой, к ней необходимо добавить заголовок:
### имя [ M, N, K ] - где: M-количество строк, N-столбцов, K-"кадров"
Минимальный заголовок: ###[] - система сама посчитает количетво столбцов в первой строке и количество строк до конца файла или следующего заголовка.
Для больших объектов поддерживается бинарный ###-формат. Возможна конвертация CSV файлов в ###-формат. Для ввода/вывода ###-формата предоставляются библиотеки на JAVA и Python.
Методы/ функции = команды системы = консольные приложения
Все команды системы являются исполняемыми файлами ( .exe в Windows ). Есть несколько команд, которые не являются консольными приложениями, они связаны с графикой: 2d - графика на основе JAVA Swing, 3d - на базе javaFX .
Ввод/вывод стэков( матрица = стэк с одним "кадром") осуществляется в стандартный поток,
который может быть перенаправлен в файл или подан на вход следующей команде.
Это позволяет объединять вызовы команд в конвейеры ("pipe-lines"). Во время работы конвейера промежуточные файлы существуют в памяти, но при необходимости их можно сохранять на диске.
Команды могут принимать на вход: стандартный поток( std.in ), файлы, параметры. Типичный конвейер :
cmd1 a b | cmd2 c - @save| cmd3 d - d:3 -:2 ... | cmdN - p > File
где: '-' обозначает стандартный поток: 1-й стэк std.in или '-:1' ;
-:k - k-й стэк в std.in ;
d:k - k-й стэк в файле d ;
@save - сохраняет std.out( промежуточный файл ) в файле: save .
Набор команд/приложений не фиксирован. Пользователь может расширять функционал на любом удобном языке программирования.
Создание консольного приложения на JAVA
Система представляет собой набор приложений( сейчас > 60 ) и библиотеку.
Приложение состоит из лончера ("Launch"- запускать) и компилированного кода. В лончер записан class-path и параметры запуска виртуальной JAVA машины( options ... ).
Все лончеры имеют одинаковый код, который берётся из эталона( размер: 16 kB - Windows, 14 kB - Linux ). Блок параметров зашифрован и имеет размер 272 байта. Он вписывается в эталон при создании лончера. Дистрибутив содержит только эталон. При установке все лончеры создаются в соответствии с файлом конфигурации за 1 секунду.
Функции лончера:
получение параметров, подготовка их к передаче в приложение ( параметры с пробелами заключаются в кавычки, строится системный вызов JVM ) ;
запуск JVM c параметрами: class-path, options ... ;
завершение лончера или ожидание завершения приложения( задаётся в конфигурации ).
Эта технология оказалась очень удобной. Новое приложение/команда создаётся вызовом
одной команды:
crex App.java # CReate EXecutable App, действия:
компиляция Java кода: App.java ( JDK/ javac должен присутствовать в системе );
App.class помещается в pik3d/lib/usr.jar ;
создаётся стандартный файл конфигурации лончера ;
запускается программа: _Launch.exe , которая вписывает конфигурацию в эталон ;
лончер ( exe-файл ): App.exe сохраняется в текущей директории .
На самом деле, можно создать лончер для любой java-программы или нестандартной конфигурации. Описание в файле документации: pik3d_DOC.html.
jj - препроцессор( "java - JAVA" ): App.jj --> App.java
Почти все приложения имеют одинаковую структуру:
import ; # "header"
class App ; # для примера будем использовать имя: App
main( arg...) #
constructor ; # "subject"
[ дополнительные классы, методы, декларации ... ].
Весь функционал("subject") реализуется в двух последних пунктах.
Неизбежно приходит мысль о "маленькой" JAVA - только "subject" и препроцессоре : "java-JAVA" = jj. "header" можно генерировать автоматически на основе имени приложения.
App.jj выглядит так:
{
java код //"subject" использует arg[] из main()
}
По сути это код конструктора: App( String[] arg ) .
При необходимости могут быть добавлены: "сверху" специфический import, комментарии ...
а "снизу" - классы, методы, свойства ...
Пример команды, которая порождает стэк (последовательность матриц) в обратном порядке: Reverse.jj :
/* Reverse Stack: > jj Reverse Stk > ktS ! save reverse stack
> jj Reverse Stk | s3d - ! display in reverse */
{
cmd("Reverse", arg, "Stk > ktS"); // check input arg[]
var stk = inStack( arg[0] ); // stk[ kk ][ii][jj]
int k = stk.length;
while(k-->0) ttFrame( stk[k], arg[0]+"_"+(k+1)); // output in reverse
}
jj-препроцессор преобразует Reverse.jj --> Reverse.java :
import java.io.*;
import java.util.*;
import pik.*;
/* Reverse Stack: > jj Reverse Stk > ktS ! save reverse stack
> jj Reverse Stk | s3d - ! display in reverse */
public class Reverse extends pik.io {
public static void main( String[] arg ){
try{new Reverse(arg);}
catch(Exception e){e.printStackTrace();eos();}
clott();
}
Reverse( String[] arg ) throws Exception
{
cmd("Reverse", arg, "Stk > ktS"); // check input arg[]
var stk = inStack( arg[0] ); // stk[ kk ][ii][jj]
int k = stk.length;
while(k-->0) ttFrame( stk[k], arg[0]+"_"+(k+1)); // output in reverse
}
}
jj-препроцессор добавляет "header", создаёт класс( App.jj --> App.java ) с методом: main() и конструктором: App( String[] arg ), строит корректную скобочную структуру: {{...}} и передаёт java-файл на исполнение. Java исполняет его как "single source file". Если задан class-path, можно вызвать программу любой сложности( не один файл ). Так как jj-препроцессор является частью системы, он знает class-path JAVA и библиотек. При вызове JAVA подставляются соответствующие параметры. jj-препроцессор может исполнять как App.java , так и App.jj ( промежуточный App.java можно сохранить) :
> jj File.java [ arg0 arg1 ... ] # исполнить java-программу
> jj File[ .jj ] [ arg0 arg1 ... ] # исполнить jj-скрипт ( > промпт консоли )
Кроме того, jj умеет исполнять последовательность java-команд прямо из командной строки :
> jj "cmd1; cmd2; ..."
Последовательность java-команд преобразуется в Noname.jj :
{
cmd1; cmd2; ...
}
а потом в Noname.java и передаётся JAVA на исполнение как "single source file".
При необходимости можно задать другое имя java-файла и сохранить его( ключ: /n=file ).
Примеры :> jj "tt("Hello World !");" # tt()=System.out.println()
> jj "for(int i=0;i<9;i++) tt(\" i=\"+i)" /n=p123 # print i=0/i=1.../i=8 # save: p123.java
Передача параметров и файл аргументов
При вызове: > jj App arg0 arg1 ... Аргументы доступны в runtime как: arg[0], arg[1] ...
Можно использовать Файл Аргументов( ФА ), тогда в качестве аргументов будут передаваться строки ФА. Вызов приобретает вид: > App File.arg Расширение: .arg - обязательно. ФА выступает в качестве конфигурационного файла и может содержать комментарии.
ФА может содержать не все аргументы. Например: 5 первых - конфигурация, а остальные -
параметры текущего вызова: > App File.arg arg5 arg6 ...
Ещё одна важная особенность ФА - он может содержать параметры, состоящие из нескольких строк. Это можно использовать при макро-подстановке фрагмента кода. Подробнее об этом в файле документации: pik3d_DOC.html .
Макро‑подстановка и запрос
Текст аргументов вызова может быть использован для макро-подстановки( МП ).
МП заменяет k-й макро-параметр на текст k-го аргумента. Если аргумент отсутствует или
пустой, макро-параметр заменяется значением по умолчанию( ЗПУ ) или удаляется, если ЗПУ отсутствует.
Макро-параметр( простой и с ЗПУ): #s#, #s/ ЗПУ #, где: s-символ: 0 | 1 | 2 ... 9 | A | B ... Z -
соответствует номеру аргумента( можно использовать a | b ... z ), всего 10+26 = 36.
Запрос: #? текст запроса/ ЗПУ # . На консоль выводится текст запроса с указанием ЗПУ
(если задан), осуществляется МП введённого текста или ЗПУ( если ввод пустой ).
Нужно чётко различать получение arg[ k ] в runtime и МП - изменение jj-текста перед компиляцией.
МП позволяет менять фрагменты кода. Например подставлять аналитическую формулу функции прямо перед посылкой jj-файла на исполнение. Пример: при построении регрессии можно задать набор функций аппроксимации в командной строке.
В стандартный набор системы входят jj-скрипты генерации значений функций заданных аналитически:
> jj Fx "exp(-x/7)*sin(x)" 0,20,.2 | vic - 1 2,11 #генерация значений( Fx.jj ) и отображение
Исполнение команд ОС в пакетном режиме
Написание системных с��риптов со сложной логикой управления для оболочек: cmd и Bash
является непростой задачей. JAVA код может оказаться проще и эффективнее.
Кроме того он одинаков в Windows и в Linux (за исключением системных команд).
Вызову приложения или конвейера в jj предшествует "$ ":
Windows: $ app1 a0 a1 a2...| app2 ... > F1 & app3 p0 p1 > F2
Linux : $ app1 a0 a1 a2...| app2 ... > F1 && app3 p0 p1 > F2
Функции shell оболочек:
cmd-shell: dir, cd, echo, md, rd, copy, del, ren, type ...
Bash-shell: ls, cd, echo, cat, cp, mv, rm, mkdir, touch ...
вызываются с # , пример( создание вектора в ###-формате ): $ #echo ###[] / 1 2 3 4 5 > vect
jj-вызов: $ cmd1 | cmd2 ... конвертируется в java text block: sys(""" cmd1 | cmd2 ... """);
Команда создания лончера: crex (см.выше) активно использует метод: sys().
crex - это скомпилированный jj-скрипт, помещён в cmd.jar и вызывается лончером: crex.exe .
Сравнение batch и jj скриптов с одинаковым функционалом( но jj можно превратить в eigenV.exe ) :
rem file: eigenV.bat | {// file: eigenV.jj
stk 77 9| noise -| ata -| eig - >V | $ stk 77 9| noise -| ata -| eig - >V
fmt V 1011.5g | $ fmt V 1011.5g
fmt V:2 1011.5g | $ fmt V:2 1011.5g
| }
> eigenV # run batch-file | > jj eigenV # run jj-скрипт
| > crex eigenV # create: eigenV.exe
| > eigenV # run лончер
Заключение
Приведен краткий обзор технологии построения проблемно-ориентированной системы на базе консольных JAVA приложений. Такой подход оправдан при
построении подобных систем ;
обработке данных ;
создании начального прототипа ;
тестировании и отладке алгоритмов ;
jj-препроцессор может быть полезен при обучении программированию на JAVA .
Система открытая. Скомпилирована JAVA-21 с использованием javaFX.
Доступны дистрибутивы для Windows и Linux ( протестирована в Linux Mint-21 ).