
Меня зовут Степан, я C# профессионал уже более 7 лет на рынке и рассказываю об этом в Telegram каналe StepOne. Иногда мне скучно на работе, потому что перекладывать JSON это слишком просто, даже если микросервисы.
Я отучился на системного программиста-компиляторщика и столкнулся с отсутствием спроса рынка на такие навыки. Но выбрал быть счастливым и написал язык программирования hydrascript, чтобы JSON гонялся даже в докере на макбуке. Решение под катом вас точно удивит!
Дисклеймер
Интерпретатор языка программирования hydrascript создан исключительно с помощью Vanilla C#. Никаких lex, flex, yacc, bison, antlr, llvm и других компиляторных инструментов не было использовано и не планируется
Лор hydrascript
Проект стартовал, как диплом бакалавра “Расширенное Подмножество JavaScript”. Я взял небольшую часть стандарта ECMA-262 и дополнил её своим авторским видением. Далее успешно закончил кафедру ИУ-9 Бауманки и решил продолжить проект под кодовым именем “HydraScript”.
После починки одного бага появляется несколько других!
Четыре года разработки по вечерам пролетели незаметно. Проект усилил мои компетенции в архитектуре и платформе .NET. Я даже изобрёл новую реализацию паттерна Visitor.
Также, open source разработка в репозитории hydrascript обрела 4 цели:
-
Частичная реализация JavaScript с объектами и сильной структурной статической типизацией без таких ключевых слов, как:
constructor,class,interface -
Публичный реверс-инжиниринг современного статического анализа (вывод типов, forward ссылки, runtime ошибки на этапе компиляции и так далее)
-
Демистификация и фасилитация компиляторного домена через исходный код HydraScript
-
Сбор понятных решений стандартных компиляторных задач (лексер, парсер, CFG, SSA, DCE и так далее)
Установка
Теперь всё серьёзно. Это больше не просто диплом студента, а полноценный open source – CI/CD на GitHub Actions, семантическое версионирование, стандарты разработки, шаблоны PR, беклог, автоматические релизы и не только.
В рамках релиза бинарник интерпретатора собирается под три платформы в режиме Native AOT, чтобы не требовать зависимостей:
-
Windows (x64)
-
macOS (arm64 Apple Silicon)
-
Linux (x64)
Последний релиз доступен на GitHub по этой ссылке. В качестве альтернативы можно поставить себе HydraScript, как утилиту .NET:
dotnet tool update --global hydrascript
Ссылка на NuGet: https://www.nuget.org/packages/hydrascript
“Киллер” фичи
Детально останавливаться на основных возможностях и конструкциях языка не хочется, чтобы не раздувать статью. Тем более, актуальная документация на английском лежит в GitHub репозитории:
Так что сейчас выделю самое главное. В основном это достижения статического анализа.
Проверка доступа к переменной до инициализации
Если в TypeScript написать программу, в которой пытаются обратиться к переменной, которой ещё не присвоили значение, то получится ошибка рантайма. Во время выполнения скомпилированного JavaScript мы получим ошибку, которую HydraScript находит ещё на этапе статического анализа:
let x = f()
function f() {
console.log(x)
return 5
}
Всегда есть значение по умолчанию
Переменные в C# требуют присваивания значения при объявлении. Если я напишу такой код, то получу ошибку Local variable ‘x’ might not be initialized before accessing
int x;
Console.WriteLine(x);
Однако, в HydraScript переменной сразу будет присвоено значение по умолчанию. Для этого интерпретатор должен вывести тип. Если тип не будет выведен, то появится ошибка статического анализа Cannot define type
let x: number
>>> x // 0
let xArr: number[]
>>> xArr // []
let s: string
>>> ~s // 0
Непересекающиеся идентификаторы символов
Символ для компилятора – это сущность в программе, которую можно использовать в исходном коде. Переменная, тип, функция, класс – и так далее. Несмотря на то, что символы могут быть разных типов, ограничения по именованию будут присутствовать. Например, такой код в C# не скомпилируется, хотя очевидно, что идентификатор x имеет разные значения:
x x = new();
x x(x x)
{
Console.WriteLine(x);
return x;
}
class x
{
x x(x x)
{
Console.WriteLine(x);
return x;
}
}
IDE покажет не одну, а сразу ТРИ ошибки:
Сейчас в HydraScript три типа символов:
-
VariableSymbol – переменная или объект
-
TypeSymbol – тип
-
FunctionSymbol – функция или метод
И уникальность идентификатора требуется в рамках типа символа. То есть скрипт может содержать тип, переменную и функцию под одним именем и успешно выполниться:
type x = number
let x:x
function x(x:x) {
>>>x
x = x 1
}
x(x)
Перекладываю JSON в Docker на MacBook через HydraScript и CGI интеграцию
С ростом возможностей HydraScript мне захотелось превратить его в нечто большее, чем просто очередной студенческий интерпретатор. Поскольку я backend разработчик, то и свой проект решил повернуть в сторону перекладывания JSON.
Довести до ума язык, чтобы на нём можно было создать веб-сервер? Пока что за рамками моих временных ресурсов. Сделать язык CGI-скриптинг совместимым? Проще пареной репы – нужно иметь в языке:
-
API для работы со строками
-
Вывод в stdout
-
Чтение переменных среды ($ENV)
-
Поддержку shebang комментариев, они же решётка строки (
#)
Что такое CGI-скриптинг
CGI-скриптинг — это набор команд, которые превращают обычную «неподвижную книжку» сайта в живую страницу: когда сервер получает твой запрос:
-
он собирает нужные данные в переменные окружения
env -
запускает скрипт через интерпретатор по пути в shebang строке. Например,
#!/usr/bin/bash -
скрипт записывает ответ в
stdout. Например,Console.WriteLineв C# -
сервер подхватывает этот поток, превращает его в веб-страницу и сразу показывает тебе на экране
Это если простыми словами. Более сложно, есть официальный стандарт RFC 3875
Или вот статья на хабре, первая в гугле по запросу “habr cgi”
Всё это я добавил в рамках релиза 2.6.0. Далее надо было достать веб-сервер с поддержкой CGI, который можно развернуть в Docker за пару команд. Оказалось, что есть образ httpd, где установлен Apache 2. Осталось докинуть в образ бинарник интерпретатора, который я решил поставить как dotnet tool:
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine as sdk-build
RUN dotnet tool update -g hydrascript
ENV PATH="/root/.dotnet/tools:${PATH}"
FROM httpd:2.4-alpine
COPY --from=sdk-build /root/.dotnet/tools/ /usr/bin
RUN apk add dotnet10-runtime
Далее собрал образ через docker-compose, докинув конфиг Apache и CGI скрипты. В качестве демонстрации я решил написать программу, которая разбирает QueryString в массив объектов имя-значение. Например, для строки a=1&b=bla&c=false сервер должен отдать JSON вида:
{
"result": [
{
"name": "a",
"value": "1"
},
{
"name": "b",
"value": "bla"
},
{
"name": "c",
"value": "false"
}
]
}
Скрипт получился большим, поэтому листинг положу под спойлер. Полный сетап с конфигом и композ файлом можно найти на GitHub в этом репозитории:
uri_parse.cgi
#!/usr/bin/hydrascript
type QueryStringParseResultItem = {
name: string;
value: string;
}
type QueryStringParseResult = {
result: QueryStringParseResultItem[];
}
type QueryStringParser = {
input: string;
}
function parse(parser: QueryStringParser): QueryStringParseResult {
const qsLen = ~parser.input
let i = 0
let items: QueryStringParseResultItem[]
let currentName: string, currentValue: string
let isName = true
while (i < qsLen) {
const currentChar = parser.input[i]
if (currentChar == "&" || i == qsLen - 1) {
let addittion = i == qsLen - 1 && currentChar != "&" ? currentChar : ""
items = items [{name: currentName; value: currentValue addittion;}]
currentName = ""
currentValue = ""
isName = true
} else if (currentChar == "=") {
isName = false
} else {
if (isName) {
currentName = currentName currentChar
} else {
currentValue = currentValue currentChar
}
}
i = i 1
}
return {
result: items;
}
}
let parser: QueryStringParser = {
input: $QUERY_STRING;
}
>>> "Content-Type: application/jsonnn"
>>> parser.parse()
Итоги
Сегодня вы узнали, как бекенд-разработчик может скучать от работы по перекладыванию JSON.
Если вы тоже устали от корпоративного булшита и недостатка rocket science, то поставьте звезду репозиторию GitHub hydrascript:
Ещё я веду Telegram канал StepOne, куда выкладываю много интересного контента о программировании на C#, даю карьерные советы, рассказываю истории из личного опыта и раскрываю все тайны IT-индустрии!


