Привет, Хабр! Хочу поделиться опытом разработки такой системы. Определяющими параметрами проблемно‑ориентированной системы являются:

  • Предметная область и характерные структуры данных

  • Набор методов/функций обработки и манипулирования данными

  • средства создания функций и написания скриптов, управляющих ходом исполнения

Предметная область - обработка и визуализация матриц и их последовательностей( стэков ).

Методы - матричные/векторные операции, линейная алгебра, регрессии, нахождение собственных значений и векторов, 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 ).

Полное описание, demo-примеры, исходники, дистрибутивы.

Комментарии (0)