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

О чём речь

My Programming System (MPS) — Это набор инструментов для обеспечения работы серверов на Linux и Windows, обрабатывающих большое количество разнообразных сетевых подключений. Вот список этих инструментов:

  • Interpretator — Интерпретатор собственного языка программирования, программа на котором будет обрабатывать запросы.

  • Language — Сам язык программирования, на котором будут писаться программы запускаемые интерпретатором.

  • Server — ПО для получения сетевых подключений, то есть просто для работы сервера.

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

Interpretator (часть 1)

Что интересно, данная идея началась с интерпретатора собственного языка программирования для удобного создания Web-сайтов, что-то типа PHP, но работающего всё время и потому не теряющего данные между подключениями, ещё в те времена когда я писал эти самые сайты на заказ... И эта идея сохранилась, но вместо Web-запросов должны обрабатываться локальные. Приведу пример:

Пользователь подключается по вашему адресу (например 123.123.123.123:80)

Любое ПО (например MPSS) принимает его и отправляет запрос к интерпретатору

Интерпретатор вызывает определённую функцию у работающей программы

Программа делает что хочет и возвращает ответ

Как вы надеюсь поняли, интерпретатор и запущенная им программа (на моём языке программирования) работает в фоне, с момента запуска и до остановки вами. Для работы с ним есть консольная команда:

mpsi — Получить базовую информацию о команде
mpsi compilation — Скомпилировать программу и классы в специальный архив
mpsi info — Получить подробную информацию об интерпретаторе и программе
mpsi request <данные запроса> — Выполнить запрос к программе
mpsi start <аргументы для программы> — Запустить программу
mpsi stop — Остановить программу

Окей, с консолью всё понятно, но где же найти код запускаемой программы? Всё просто, загляните в корневой каталог и найдите там папку mps, у неё будет примерно такая структура:

mps/
  interpretator/ — Каталог для файлов интерпретатора
    program/ — Каталог для файлов программы
      *.mpsp — Файл с кодом программы
      *.mpsc — Файлы с кодом классов программы
    modules/ — Каталог для модулей
      *.mpsm — Модули для интерпретатора
    program.log — Файл с логом программы
  server/ — Каталог для файлов сервера
    data/ — Каталог для данных сервера
    config.json — Конфигурационный файл сервера

Ничего не понятно?) Что за классы? Что за сжатые данные? Что за логи? Что за конфиги? Чтобы ответить на эти вопросы, давайте перейдём к самому языку программирования!

Language

Структура программы проста, есть основной код (самой программы), а есть код классов, которые работают отдельно и у которых программа может вызывать функции (то есть программа + дополнительные программы-помощники). Что насчёт самого кода, у него очень простая необязательная структура: Препроцессорнные функции, объявление глобальных переменных, функции (а точнее методы, об этом см. дальше).

Давайте по порядку, есть всего 4 препроцессорнных функции, одна из которых недоступна в классах (вы сами поймёте почему):

#define <название> <значение> — Начать заменять все совпадения с "названием" на "значение"
#import <путь к файлу> [имя] — Подключить класс и если указан принудительно установить имя (именно эта функция недоступна в классах)
#setting <название> <значение> — Изменить настройки программы/класса (об этом позже)
#undefine <название> — Прекратить замену совпадений

Сразу скажу, define и undefine я позаимствовал у любимой мною C-шки, так что думаю останавливаться на них нет смысла. По поводу import хочу сказать только что она поддерживает любые пути к файлам с кодами классов, хоть локальные (например "my_super_class.mpsc" либо "/path/to/my_super_class.mpsc"), хоть web (например "https://my-site.ru/my_super_class.mpsc"), хоть ftp (например "ftp://root:my-password@my-ftp.ru/my_super_class.mpsc"). А вот о setting нужно рассказать подробнее, у программы и классов есть некоторые параметры, которые они могут изменять:

(string) NAME — Название программы/класса (по умолчанию название файла с кодом без расширения)
(string) DESCRIPTION — Описание программы/класса (по умолчанию "")
(string) VERSION — Версия программы/класса (по умолчанию "1.0")

Дополнительно для классов:
(boolean) STATIC — Статический ли класс (по умолчанию true)

Зачем они? Во первых они доступны как системные макросы (то есть те которые созданы заранее, но вы можете их переопределить как и любые другие (просто вызвав define снова с тем же названием)):

Доступно везде (и в программе, и в классах):
MODULES -> Массив подключённых модулей (это массив массивов, содержащих название, описание и версию каждого модуля)
PROGRAM_NAME -> Название программы
PROGRAM_DESCRIPTION -> Описание программы
PROGRAM_VERSION -> Версия программы
SYSTEM_VERSION -> Версия интерпретатора

Доступно лишь в программе:
CLASSES -> Массив подключённых классов (это массив массивов, содержащих название, описание и версию каждого класса)

Доступно лишь в классах:
CLASS_NAME -> Название класса (того в котором замена)
CLASS_DESCRIPTION -> Описание класса (того в котором замена)
CLASS_VERSION -> Версия класса (того в котором замена)
CLASS_STATIC -> Статичный ли класс (тот в котором замена)

Во вторых, это просто прикольно и можно где-нибудь использовать :) Теперь, прежде чем перейти к синтаксису языка, давайте разберём какие вообще есть типы данных и как с ними работать:

array — Массив данных ([...])
boolean — Логическое значение (true | false)
char — Одиночный символ ('?')
class — Экземпляр динамического класса (того у которого параметр STATIC равен false)
double — Десятичное число (?.?)
empty — Ничего (null)
integer — Целое число (?)
string — Строка ("...")

Ну а использовать их можно как и в большинстве языков программирования, так что я просто приведу несколько примеров:

variable @example = [
  "строковой ключ" : "значение",
  65456 : "← цифровой ключ, значение"
]; // array
@example = ExampleClass(); // class
@example = null; // empty

Но вас наверняка интересует, динамическая или статическая типизация? Ответ, динамическая.

Таблица "превращений"
array -> boolean: если нет элементов, то false, иначе true
array -> char: 'a'
array -> class: класс Array
array -> double: если нет элементов, то -1.0, иначе 1.0
array -> empty: null
array -> integer: если нет элементов, то -1, иначе 1
array -> string: "array(<элементы массива через запятую>)"
boolean -> array: [<значение>]
boolean -> char: 'b'
boolean -> class: класс Boolean
boolean -> double: если false, то -1.0, иначе 1.0
boolean -> empty: null
boolean -> integer: если false, то -1, иначе 1
boolean -> string: "boolean(<значение>)"
char -> array: [<значение>]
char -> boolean: true
char -> class: класс Char
char -> double: 1.0
char -> empty: null
char -> integer: 1
char -> string: "char(<значение>)"
class -> array: [<значение>]
class -> boolean: true
class -> char: 'c'
class -> double: 1.0
class -> empty: null
class -> integer: 1
class -> string: "class(<название класса>)"
double -> array: [<значение>]
double -> boolean: если меньше 0.0, то false, иначе true
double -> char: 'd'
double -> class: класс Double
double -> empty: null
double -> integer: <целая часть значения>
double -> string: "double(<значение>)"
empty -> array: []
empty -> boolean: false
empty -> char: 'e'
empty -> class: класс Empty
empty -> double: 0.0
empty -> integer: 0
empty -> string: "null"
integer -> array: [<значение>]
integer -> boolean: если меньше 0, то false, иначе true
integer -> char: 'i'
integer -> class: класс Integer
integer -> double: <значение>.0
integer -> empty: null
integer -> string: "integer(<значение>)"
string -> array: [<символы строки отдельными элементами>]
string -> boolean: если нет символов, то false, иначе true
string -> char: 's'
string -> class: класс String
string -> double: если нет символов, то -1.0, иначе 1.0
string -> empty: null
string -> integer: если нет символов, то -1, иначе 1

Ну и наконец, можно переходить к синтаксису!

#<строка>; — Вызов препроцессорной функции
<строка>([аргументы]); ЛИБО <строка>.<строка>([аргументы]); ЛИБО @<строка>.<строка>([аргументы]); — Вызов метода
//<строка>\n — Однострочный комментарий
/*<строка>*/ — Многострочный комментарий
variable @<строка>; ЛИБО variable @<строка> = <значение>; — Объявление переменной
method <строка>([аргументы]) {[тело]}; — Объявление метода
if (<условие>) <тело> ЛИБО if (<условие>) {[тело]}; — Выполнить тело при условии
elseif (<условие>) <тело> ЛИБО elseif (<условие>) {[тело]}; — Выполнить тело при условии и если предыдущий if не выполнился
else <тело> ЛИБО else {[тело]}; — Выполнить тело при условии и если все предыдущие if и elseif не выполнились
loop (<условие>) {[тело]}; ЛИБО loop (<условие>) <тело> ЛИБО {[тело]} loop (<условие>); ЛИБО <тело> loop (<условие>);— Выполнять код повторно пока условие верно
return <строка>; ЛИБО return; — Вернуть значение либо null
continue; — Пропустить выполнение оставшегося тела в цикле
break; — Прекратить работу цикла
static <строка>; — Сделать переменную либо метод статической (в программе: сохранить при остановке и загрузить при запуске, в классе: сделать доступной всем экземплярам)
dynamic <строка>; — Сделать переменную либо метод динамической (в программе: ничего, в классе (недоступно в статическом классе): сделать индивидуальной для каждого экземпляра)
public <строка>; — Сделать переменную либо метод доступным из вне
protected <строка>; — Сделать переменную либо метод недоступным из вне, но доступным для системы
private <строка>; — Сделать переменную либо метод недоступным из вне
@<строка> = <строка>; — Присвоение значения переменной
@<строка> += <строка>; — Прибавление числа к значению переменной и присвоение результата
@<строка> -= <строка>; — Вычитание числа из значения переменной и присвоение результата
@<строка> *= <строка>; — Умножение числа на значение переменной и присвоение результата
@<строка> **= <строка>; — Возведение значения переменной в степень число и присвоение результата
@<строка> /= <строка>; — Деление значения переменной на число и присвоение результата
@<строка> \= <строка>; — Деление значения переменной на число и присвоение остатка
(<строка> == <строка>) — Если объекты равны
(<строка> === <строка>) — Если типы объектов равны
(<строка> != <строка>) — Если объекты не равны
(<строка> !== <строка>) — Если типы объектов не равны
(<строка> > <строка>) — Если первое число больше второго
(<строка> >= <строка>) — Если первое число больше либо равно второму
(<строка> < <строка>) — Если первое число меньше второго
(<строка> <= <строка>) — Если первое число меньше либо равно второму
<условие> && <условие> — Условие верно если оба условия верны
<условие> ^^ <условие> — Условие верно если верно либо первое условие, либо второе (но не оба)
<условие> || <условие> — Условие верно если хоть одно из условий верно
<число> + <число> — Сложение двух чисел
<число> - <число> — Вычитание второго числа из первого
<число> * <число> — Умножение чисел
<число> ** <число> — Возведение первого числа в степень второе число
<число> / <число> — Деление первого числа на второе
<число> \ <число> — Деление первого числа на второй и получение остатка
-<число> — Смена знака числа
!<строка> — Поменять на противоположное логическое значение
method <строка>([аргументы, ]array @<строка>...) — Все аргументы после объявленых аргументов поместить в массив @<строка>
method <строка>([аргументы, ]<строка> @<строка> = <строка>[, аргументы]) — Если не указан аргумент то присвоить ему значение
method <строка>([аргументы, ]@<строка>[, аргументы]) — Разрешить передавать аргумент любого типа
method <строка>([аргументы, ]<строка> @<строка>[, аргументы]) — Разрешить передовать аргумент только определённого типа
method <строка>([аргументы, ]<строка>|<строка> @<строка>[, аргументы]) — Разрешить передовать аргумент одного из нескольких типов

Ну и в завершение описания MPSL, нужно обязательно упомянуть системные классы и методы (то есть функции). Системные классы не надо подключать, они все (на данный момент) статичные и их можно использовать также как обычные.

Список системных классов
Array — Класс для работы с массивами {
    exist(array @array, string | integer @key) — Проверить есть ли значение с ключём key в массиве array
    key(array @array, @value) — Получить ключ по значению value из массива array
    merge(array @arrays...) — Объеденить неограниченное количество массивов
    search(array @array, @value) — Проверить есть ли ключ со значением value в массиве array
    size(array @array) — Получить количество значений в массиве array
    sort(array @array, integer @type) — Отсортировать массив array
}
Char — Класс для работы с одиночными символами {
    convert(integer @type, char @char) — Конвертировать в другую кодировку символ
    get(integer @number) — Получить символ по номеру
    toLower(char @char) — Перевести символ в нижний регистр
    toUpper(char @char) — Перевести символ в верхний регистр
}
Class — Класс для работы с экземплярами классов {
    clone(class @class) — Создать новый экземпляр того же класса что и class
    destroy(class @class) — Уничтожить экземпляр класса class
}
Double — Класс для работы с десятичными числами {
    getDecimal(double @double) — Получить целую часть десятичного числа double
    getWhole(double @double) — Получить десятичную часть десятичного числа double
    max(double @double1, double @double2) — Получить наибольшее число из двух десятичных
    maxValue() — Получить максимальное возможное десятичное число
    min(double @double1, double @double2) — Получить наименьшее число из двух десятичных
    minValue() — Получить минимальное возможное десятичное число
    round(double @double) — Округлить десятичное число
}
Integer — Класс для работы с челыми числами {
    max(integer @integer1, integer @integer2) — Получить наибольшее число из двух целых
    maxValue() — Получить максимальное возможное целое число
    min(integer @integer1, integer @integer2) — Получить наименьшее число из двух целых
    minValue() — Получить минимальное возможное целое число
}
Language — Класс для работы с кодом на других языках программирования {
    execute(string @language, string @code) — Выполнить код на другом языке программирования
    exist(string @language) — Проверить можно ли выполнить код на таком языке программирования
}
Log — Класс для работы с логом {
    read(integer @lines) — Получить lines строк из лога, считая с низу
    size() — Получить количество строк в логе
    write(string @line) — Записать строку в лог
}
Network — Класс для работы с сетью {
    connect(integer @type, string @address) — Подключиться к серверу address
    createServer(integer @type, string @address, array @settings) — Создать сервер по адресу address с настройками settings (какие методы будут вызываться и когда, время ожидания ответа и т.д.) типа type
    destroyServer(integer @id) — Уничтожить сервер id
    disconnect(integer @id) — Отключиться от сервера id
    read(integer @id) — Получить входящие данные от сервера id
    write(integer @id, string @packet) — Отправить данные серверу id
}
Random — Класс для работы со случайными данными {
    boolean() — Получить случайное значение типа boolean
    char(string @chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,@#$_&-+()/*\"':;!?~|^={}\\%[]<>") — Получить случайное значение типа char
    double(double @min = Double.minValue(), double @max = Double.maxValue()) — Получить случайное значение типа double
    integer(integer @min = Integer.minValue(), integer @max = Integer.maxValue()) — Получить случайное значение типа integer
    string(integer @length, string @chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,@#$_&-+()/*\"':;!?~|^={}\\%[]<>") — Получить случайное значение типа string
    time(string @format = "d.m.y s:M:h", string @min = Time.minValue(@format), string @max = Time.maxValue(@format)) — Получить случайное значение типа time
}
Security — Класс для обеспечения безопасности чего-либо {
    decrypt(integer @type, string @string, string @key) — Дешифровать строку string с помощью ключа key алгоритмом type
    encrypt(integer @type, string @string, string @key) — Зашифровать строку string с помощью ключа key алгоритмом type
    hash(integer @type, string @string) — Хешировать строку string алгоритмом type
}
Storage — Класс для работы с хранилищами {
    copy(string @old_path, string @new_path) — Копировать каталог/файл в другое место
    createDir(string @path, integer @permissions) — Создать новый каталог
    createFile(string @path, integer @permissions) — Создать новый файл
    exist(string @path) — Проверить существует ли каталог/файл
    info(string @path) — Получить информацию о каталоге/файле
    isDir(string @path) — Проверить является ли каталогом
    isFile(string @path) — Проверить является ли файлом
    list(string @path) — Получить список файлов в каталоге
    lock(string @path) — Заблокировать для чтения и записи файл
    read(string @path) — Получить содержимое файла
    remove(string @path) — Удалить каталог/файл
    rename(string @path, string @name) — Переименовать каталог/файл
    unlock(string @path) — Разблокировать для чтения и записи файл
    write(string @path, string @content) — Записать строку в файл
}
String — Класс для работы со строками {
    convert(integer @type, string @string) — Конвертировать в другую кодировку строку
    toLower(string @string) — Перевести все символы в строке в нижний регистр
    toUpper(string @string) — Перевести все символы в строке в верхний регистр
    length(string @string) — Получить количество символов в строке
    search(string @string, string | char @search) — Проверить присутствует ли в строке текст
    size(string @string) — Получить количество байт в строке
    substring(string @string, integer @start_position = 0, integer @end_position = String.length(@string) - 1) — Получить содержимое строки со start_position до end_position
    replace(string @string, string @search, string @value) — Заменить все совпадения в строке
}
System — Класс для работы с интерпретатором, программой и классами {
    consoleExecute(string @command) — Выполнить команду в консоли
    getLocale() — Получить системный язык
    getOS() — Получить название и версию текущей ОС
    import(string @path) — Подключить класс
    loadModule(string @path) — Загрузить модуль
    stop() — Остановить работу программы
}
Thread — Класс для работы с потоками {
    create(string @method) — Создать отдельный поток, который выполнит метод method
    destroy(integer @id) — Уничтожить поток id
    join(integer @id) — Дождаться выполнения потока id
    list() — Получить список существующих потоков и их id
    start(integer @id) — Запустить поток id
}
Time — Класс для работы с датами и временем {
    get(string @format = "d.m.y s:M:h") — Получить текущее время в определённом формате
    getTimeZone() — Получить текущую временную зону
    convert(string @old_format, string @new_format, string @time) — конвертировать дату из одного формата в другой
    max(string @format, string @time1, string @time2) — Получить наибольшее время из двух предоставленных в одном формате
    maxValue(string @format = "d.m.y s:M:h") — Получить максимальное возможное время в определённом формате
    min(string @format, string @time1, string @time2) — Получить наименьшее время из двух предоставленных в одном формате
    minValue(string @format = "d.m.y s:M:h") — Получить минимальное возможное время в определённом формате
}

Ну а системные методы, это четыре для программы и два для классов метода, которые обязательно должны присутствовать и которые СОВЕТУЕТСЯ НЕ ВЫЗЫВАТЬ ГДЕ-ЛИБО В КОДЕ:

Для программы:
protected dynamic method Error(integer @code, array @arguments) — Вызывается при ошибке во время работы программы (code- код ошибки, arguments- аргументы ошибки (строка, символ, сообщения и т.д.))
protected dynamic method Request(integer @type, array @arguments) — Вызывается при запросе к программе (type- тип запроса (внешний/системный), arguments- аргументы запроса))
protected dynamic method Start(array @arguments) — Вызывается при запуске программы (arguments- аргументы переданные при запуске)
protected dynamic method Stop() — Вызывается при остановке программы

Для классов:
protected dynamic method Constructor(array @arguments = []) — Вызывается если статический класс, то при запуске программы, иначе при создании экземпляра (arguments- аргументы запуска/переданные при создании экземпляра)
protected dynamic method Destructor() — Вызывается если статический класс, то при остановке программы, иначе при уничтожении экземпляр

Interpretator (часть 2)

Прежде чем начать читать

Вообще, я изначально не хотел делить раздел Interpretator на две части, но так как алгоритм интерпретации кода на моём языке программирования будет не понятен без знания основ самого языка, мне пришлось сделать эту ужасную вещь :)

Раз вы теперь знаете как работают программа и классы на моём языке программирования, то можно узнать что происходит во время запуска, то есть, во время интерпретации. Как ни странно, всё начинается с... запуска интерпретатора! Он обрабатывает введённые аргументы и если ему сказано запустить программу проверяет не запущена ли уже она, если да, то выдаёт ошибку, если нет, то "заглядывает" в каталог /mps/interpretator/program/ и ищет там файл с расширением .mpsp, если находит несколько, то выдаёт ошибку, если не находит вообще, то начинает искать файл .mpsa, о котором мы поговорим позже, ну и если всё ок, то получает содержимое (если не получилось либо в нём ничего нет, то выдаёт ошибку :) и передаёт его препроцессору, начиная тем самым интерпретацию:

Препроцессор получил данные и начинает их обработку, которая делится на два этапа:

  1. Удаляются все комментарии и выполняются все препроцессорные функции ПОЛНОСТЬЮ ИГНОРИРУЯ КОД.

  2. Код дополняется где нужно, приводя его в единый вид (например "if (...) ...;" будет заменено на "if (...) {...};"), но уже ничего не игнорируя.

Что на что меняется
Для программы:
variable...; -> public dynamic variable...;
method...; -> public dynamic method...;
static variable...; -> public static variable...;
static method...; -> public static method...;
dynamic variable...; -> public dynamic variable...;
dynamic method...; -> public dynamic method...;
public variable...; -> public dynamic variable...;
public method...; -> public dynamic method...;
protected variable...; -> protected dynamic variable...;
protected method...; -> protected dynamic method...;
private variable...; -> private dynamic variable...;
private method...; -> private dynamic method...;
return; -> return null;
else if... -> elseif...
if (...) ...; -> if (...) {...};
elseif (...) ...; -> elseif (...) {...};
else ...; -> else {...};
loop (...) ...; -> loop (...) {...};
... loop (...); -> {...} loop (...);

Для классов:
variable...; -> public static variable...;
method...; -> public static method...;
static variable...; -> public static variable...;
static method...; -> public static method...;
dynamic variable...; -> public dynamic variable...;
dynamic method...; -> public dynamic method...;
public variable...; -> public static variable...;
public method...; -> public static method...;
protected variable...; -> protected static variable...;
protected method...; -> protected static method...;
private variable...; -> private static variable...;
private method...; -> private static method...;
return; -> return null;
else if... -> elseif...
if (...) ...; -> if (...) {...};
elseif (...) ...; -> elseif (...) {...};
else ...; -> else {...};
loop (...) ...; -> loop (...) {...};
... loop (...); -> {...} loop (...);

Тажке вставляются пробелы где нужно, удаляются переносы строк, линие табы и лишние пробелы, добавляются return; в методы где они отсутствуют.

Дальше в дело вступает анализатор, он проверяет весь код и если где-то что-то не так (например не хватает точки с запятой) выдаёт ошибку. Потом, отформатированные и проверенный код передаётся лексеру, который разбивает его на токены и передаёт оптимизатору, который как понятно из названия оптимизирует их как может. Потом, уже оптимизированные токены получает парсер, он составляет из них специальные структуры (или если хотите таблицы) и передаёт их воркеру, который в свою очередь сохраняет их где нужно и вызывает метод Start с полученными в самом начале аргументами, а, и не подумайте что классы не интерпретируются, они проходят такую же цепочку. Помните я упоминал какие-то файлы с расширением .mpsa? Это архивы, в которые можно сохранить специальные структуры, создаваемые парсером. Если указать аргументом compilation вместо start, произойдёт то же самое (без поиска файлов .mpsa), но парсер не отдаст сгенерированные структуры воркеру, а сохранит их в файл, чтобы можно было при частом запуске экономить время.

Server

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

{
  "<название сервера>": {
    "address": "<адрес этого сервера>",
    "port": <порт этого сервера>,
    "type": "<тип этого сервера (пока что поддерживаются: web, ftp, ssh)>",
    "settings": { // Этот объект имеет ещё индивидуальные значения для каждого типа серверов
      "mpsi": true
    }
  }
}

Вопросов стало только больше? Это хорошо, в "самом верхнем" объекте хранятся объекты с информацией о серверах, которые должен создавать MPSS. Например такой конфиг:

{
  "my_web_server": {
    "address": "127.0.0.1",
    "port": 80,
    "type": "web",
    "settings": {
      "mpsi": false,
      "php": true,
      "perl": true
    }
  },
  "my_ssh_server": {
    "address": "127.0.0.1",
    "port": 1234,
    "type": "ssh",
    "settings": {
      "mpsi": true
    }
  }
}

Создаст web-сервер на порту 80, поддерживающий PHP и Perl скрипты, и ssh-сервер запросы к которому будет обрабатывать MPSI. Вы спросите, где же данные для web-сервера? А они хранятся в каталоге /mps/server/data/<название сервера>/ или же в нашем случае в /mps/server/data/my_web_server/. Но если с ним всё вроде бы понятно, со вторым есть пара интересных моментов, и чтобы их объяснить давайте попробуем подключиться к нему (для этого я буду использовать обычный SSH-клиент на Linux из консоли). Что по вашему произойдёт на "стороне сервера" после ввода этих команд:

ssh root@127.0.0.1 -p 1234

Думаете MPSS запросит пароль? Или вообще не будет запрашивать? Нет, он отправит запрос MPSI, что будет идентично такому коду на MPSL:

Request(1, [
  "header" : "MPSS SSH-SERVER",
  "type" : 0, // Тип: авторизация
  "data" : "root:localhost" // Имя пользователя, от кого
]);

Если он вернёт false либо -1, то клиента пошлют нафиг клиенту просто откажут в доступе, если true либо 0, то ему скажут что всё окей, а если 1, то запросят пароль. Предположим что в нашем случае программа вернула 0 и я (как клиент) получил это:

root@127.0.0.1's password:

И я введу например "my-super-mega-password", то MPSS опять сделает запрос к MPSI:

Request(1, [
  "header" : "MPSS SSH-SERVER",
  "type" : 1, // Тип: подтверждение пароля
  "data" : "my-super-mega-password" // Полученный от клиента пароль
]);

Теперь, если вернут false либо -1, то мне скажут что пароль неверный, а если true либо 1, то скажут что всё окей. И давайте предположим что программа вернула 1 и мне "пустили на сервер", а потому сразу введём какую-нибудь команду (:

rm -rf /

Думаете MPSS её выполнит? Или сделает запрос к нашей программе? Если вы подумали второе, то вы уже начинаете понимать, ведь это правда и будет выглядеть вот так:

Request(1, [
  "header" : "MPSS SSH-SERVER",
  "type" : 2, // Тип: выполнение команды
  "data" : "rm -rf /" // Сама команда
]);

В этот раз возврат должен быть немного другим, если false либо -1, то нас просто отключат, если же строка, то её вернут как ответ. Только для примера давайте нам вернут это:

Дорогой ssh-клиент, иди ка отсюда с такими командами! С уважением, программа на MPSL.

Надеюсь с этим всё понятно, так что давайте посмотрим на команду:

mpss — Получить базовую информацию о команде
mpss info <название сервера> — Получить подробную информацию о сервере
mpss start <название сервера> — Запустить сервер
mpss stop <название сервера> — Остановить сервер

Ну что-ж, думаю пояснять смысл аргументов команды не стоит, да и про файловое устройство рассказывать не надо (ведь в разделе "Interpretator (часть 1)" я уже сказал про него всё важное), а так как статья и так получилась слишком большая, будем уже закругляться.

Дополнительно

Расширения файлов и их значение:

.mpsp — Код программы на MPSL
.mpsc — Код класса на MPSL
.mpsa — Архив со специальными структурами программы и классов на MPSL
.mpsm — Код модуля для MPSI

Подробная документация по всему этому когда-нибудь появится.

Заключение

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

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