Search
Write a publication
Pull to refresh
5
0
Иван @Adler3D

Автор

Send message

Поток сознания — это не ошибка, а:
символ искусственного разума, перебирающего бесконечные варианты выбора.
отражение многослойной природы принятия решений в неопределённой среде.
инструмент передачи идеи живучести, нестабильности и адаптивности кода.
форма мышления, открывающая путь к глубокой адаптивности стратегий.

Критика нужна, но наверно лучше ещё сосредоточиться на сути, а не форме выражения.

Вы всё правильно поняли.

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

Как-то у тебя всё сложно, я например просто взял официальные исходники симулятора, оптимизировал его, затем прикрутил генератор планов, переборщик планов, мутатор планов, оценочную функцию, алгоритм урезания мощности переборщика планов для избежания TL, оценщик вероятностей ходов соперников для оценочной функции. И вроде бы на этом всё. Никакой супер сложной математики движения амёб не пришлось писать.

я тут баги нашёл и опечатки вот исправленный вариант:

t_bool{string s=any_str_from_vec(split("true,false",","));}
t_root{t_bool b;}

// с возможностью парсить еще и числа
t_bool:i_value{string s=any_str_from_vec(split("true,false",","));}
t_num:i_value{string s=any(gen_dips("09"));}
t_root{TAutoPtr<i_value> value;}

// про массив разделенный запятыми
t_bool:i_value{string s=any_str_from_vec(split("true,false",","));}
t_num:i_value{string s=any(gen_dips("09"));}
t_arr:i_value{
  t_num_arr_val:i_arr_val{t_num n;}
  t_bool_arr_val:i_arr_val{t_bool b;}
  vector<TAutoPtr<i_arr_val>> arr=vec(",");// используем i_arr_val вместо i_value чтобы избежать рекурсии.
}
t_root{TAutoPtr<i_value> value;}

// опциональные префиксы
t_optional_test{t_opt{"optional_prefix "} TAutoPtr<t_opt> prefix?; t_num num;}
t_root{t_optional_test value;}

// парсер комментариев
t_your_comment{
  "/*"
  string body=end("*/");
}

Да, всё, что можно описать в ANTLR, можно описать и в QapDSLv2.

По производительности оба инструмента примерно сопоставимы, но в моих тестах с парсингом JSON`а QapDSLv2 быстрее чем ANTLR в 3.52 раза.

Да, QapDSLv2 полностью поддерживает все регулярные грамматики, поскольку регулярные грамматики — это самый простой класс формальных грамматик, и их поддержка — базовая возможность любого генератора парсеров.

QapDSLv2+QapGen — это гораздо более мощный и гибкий инструмент, чем PEG. В отличие от PEG, который вроде является формализмом для однозначного синтаксического анализа с приоритетным выбором, QapDSLv2 позволяет описывать сложные контекстно-свободные конструкции, интегрируя грамматику с построением AST, сериализацией и трансформациями.

А что-то вроде Паскаля / Бейсика можно сделать на нем?

Конечно можно. Это же не какие-то питоны со своими пробелами.
Вот пример с чего можно начать(это простой парсер Delphi7 модулей, парсит все объявления, а всю имплментацию пока просто загоняет в строку):

t_sep{
  t_comment:i_sep{"{" string s=end("}");}
  t_c_comment:i_sep{"//" string body=any(dip_inv("\n\r"));}
  t_raw_sep:i_sep{string s=any(" \r\n\t");}
  string value=str<vector<TAutoPtr<i_sep>>>();
}
using " " as t_sep;
t_name{string s=any(gen_dips("azAZ09"));}
t_name_with_sep{" "? t_name n; " "?}
t_class_decl:i_type_decl{
  t_of:i_class_impl{"of" " "? t_name n; " "? ";"}
  t_semicolon:i_class_impl{";"}
  t_class_body:i_class_impl{
    "("
    " "?
    vector<t_name_with_sep> interfaces=vec(",");
    " "?
    ")"
    string body=end("end;"); // заглушка, просто читаем всё до конца пока.
  }
  t_name name;
  " "?
  "="
  " "?
  "class"
  " "?
  TAutoPtr<i_class_impl> impl?;
}
t_field{" "? t_name name; " "? ":" " "? t_name type; " "? ";" " "?}
t_record_decl:i_type_decl{
  t_name name;
  " "?
  "="
  " "?
  "record"
  " "?
  vector<t_field> fields?;
  "end"
  " "?
  ";"
}
t_proc_decl:i_type_decl{
  t_of{" "? "of" " "? t_name n; " "?}
  t_name proc_name;
  " "?
  "="
  " "?
  "procedure"
  " "?
  "("
  t_name param_name;
  " "?
  ":"
  " "?
  t_name param_type;
  ")"
  TAutoPtr<t_of> of?;
  ";"
}
t_type_ptr_decl:i_type_decl{
  t_name name;
  " "?
  "="
  " "?
  "^"
  t_name type;
  " "?
  ";"
}
t_array_decl:i_type_decl{
  t_name name;
  " "?
  "="
  " "?
  "array"
  " "?
  "["
  " "?
  t_name from;
  " "?
  ".."
  " "?
  t_name to;
  " "?
  "]"
  " "?
  "of"?
  " "?
  t_name type;
  " "?
  ";"
}
t_sep_type_decl:i_type_decl{" "}
t_types:i_decl{
  "type"
  " "?
  vector<i_type_decl> arr;
}
t_var:i_decl{
  "var"
  vector<t_field> fields;
}
t_func:i_decl{
  t_ret_type{":" t_name t;}
  string kb=any_str_from_vec(split("function,procedure",","));
  " "?
  t_name name;
  " "?
  "("
  string params=end(")"); // заглушка
  " "?
  TAutoPtr<t_ret_type> type?;
  " "?
  ";"
}
t_sep_decl:i_decl{" "}
t_unit{
  "unit"
  " "?
  t_name name;
  ";"
  " "?
  "interface"
  " "?
  "uses"
  vector<t_name_with_sep> modules_decl=vec(",");
  ";"
  " "?
  vector<i_decl> decls_decl?;
  "implementation"
  " "?
  "uses"
  vector<t_name_with_sep> modules_impl=vec(",");
  ";"
  " "?
  vector<i_decl> decls_impl?;
  string body=end("end."); // заглушка
}

писал быстро(2.5 часа) из которых 40 минут грамматика, а всё остальное отладка грамматики.

тестировал вот на этом файле: Basa.pas

Очень необычный синтаксис. Не хватает простых примеров, на которых можно будет по шагам разобраться какие конструкции чему соответствуют. Например, начать с простого парсера который парсит только true и false, затем добавить возможность парсить еще и числа, затем массив разделенный запятыми, потом показать как работать с кардинальностью (? / + / *) и альтернативами. Дальше уже разные типы токенов, комментарии и другие возможности парсера.

Вот сделал:

//парсим только true и false
t_bool{string s=any_str_from_vec(split("true,false",",");}
t_root{t_bool b;}

// с возможностью парсить еще и числа
t_bool:i_value{string s=any_str_from_vec(split("true,false",",");}
t_num:i_value{string s=any(gen_dips("09"));}
t_root{TAutoPtr<i_value> value;}

// про массив разделенный запятыми
t_bool:i_value{string s=any_str_from_vec(split("true,false",",");}
t_num:i_value{string s=any(gen_dips("09"));}
t_arr:i_value{vector<TAutoPtr<i_value>> arr=vec(",");}
t_root{TAutoPtr<i_value> value;}

// опциональные префиксы
t_optional_test{t_opt{"optional_prefix "} t_opt prefix?; string num=any(gen_dips("09"));}
t_root{t_optional_test value;}

// парсер комментариев
t_your_comment{
  "/*"
  string body=end("*/");
}

Каким образом у вас реализовано восстановление после обнаружения ошибки?

Пока парсер просто при ошибке завершает свою работу и выдаёт на выходе сообщение о том где парсинг остановился.

Например что будет если пользователь передаст в ваш парсер вот такой JSON: { "array": [ 1, 2 3, 4], "object": { "a": true "b": null } }.

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

{"ok":false,"date":"2025.07.09 17:34:37"}@@@{"offset":15}
{"line":0,"pos":15}
"{ \"array\": [ 1, 2 3, 4], \"object\": { \"a\": true \"b\": null } }" EOD
------------------^

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

Не, пока я ещё до такой круто-ты не дошёл, но планирую так сделать. В этом вроде нет ничего сложного, работы на несколько часов.

Я не совсем понимаю как работают правила t_true, t_null, t_string и т.д. Какой объект я получаю, когда пишу подобное правило

Это наследник полиморфного лексера, их вызывают когда какой-то лексер завернул поддерживаемый ими интерфейс в умный указатель, например вот так:TAutoPtr<i_value>

как мне получить информацию о том, в каком месте исходного текста находится указанный токен.

Пока такое не реализовано, но в этом тоже не особо много трудностей/работы. Тоже планирую такое сделать.

В идеале у меня должен быть доступ не только с пробельным символам, но и к следующему/предыдущему токенам.

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

Не нашел ничего про инкрементальный парсинг. Это важный момент для работы с большими файлами.

Большие файлы, это не то для чего предназначен мой генератор парсеров. Поэтому такой фишке пока нет и не особо-то планируется, но если вам надо то я могу такое сделать.

Очень необычный синтаксис.

Это почти обычный С++ код. Из отличий только то что можно вставлять символы которые надо поглотить и то что писать struct не обязательное требование.

Не хватает простых примеров, на которых можно будет по шагам разобраться какие конструкции чему соответствуют. Например, начать с простого парсера который парсит только true и false, затем добавить возможность парсить еще и числа, затем массив разделенный запятыми, потом показать как работать с кардинальностью (? / + / *) и альтернативами. Дальше уже разные типы токенов, комментарии и другие возможности парсера.

Классная идея, спасибо за неё, обязательно такое сделаю.

Но есть и другие задачи, при которых парсинг вообще не нужен, например, генерация кода. AST создается генератором, потом трансформируется в исходник.

Согласен с тем что есть такие задачи, но даже в них можно пристроить парсинг, например можно написать небольшой промежуточный язык и его интерпретатор/обходчик, а затем генерировать низкоуровневый код не вручную, а через этот промежуточный язык. Просто делаешь emit("***код на промежуточном языке***"+_какие_то_переменные) и интерпретатор этого промежуточного языка делают всё низкоуровневую работу по генерации кода самостоятельно. Такой подход помогает спускаться к машкоду не сразу, а через простое промежуточное звено. Это даёт простор для оптимизации. Я пыталься сразу генерировать машкод, но у меня не особо это получилось. А вот с промежуточным звеном - всё отлично. Правда в моей задачи всё же был парсинг С++ подобного языка на верху стека, так что пример возможно не сильно корректный. Я плохо представляю как и где применяется ручная генерация AST и затем последующая трансформируется в исходник/код. Можно какой-нибудь пример? Мне на ум приходит только сериализация состояния программы. Но это вообще никак с парсингом не связано.

Поэтому на мой взгляд совмещать описание AST с описанием грамматики - неправильно.

Как по мне, так это вообще киллер фича моего подхода, т.к похоже никто кроме меня так не делает. И зря. Это очень-очень удобно.

генератором синтаксических анализаторов (парсеров), например, ANTLR.

ANTLR медленный по моим замерам. Где-то в 3.32 раза медленнее моего текущего решения.

Ну это надо вручную писать, можно ошибиться, к чему эта ручная работа, если у меня это делается автоматически?

можно же просто жадно сожрать всю эту шаблоноту в std::string применяя простые правила поедания С++ кода, а затем когда наступит семантический анализ и будут известны все типы в текущем месте обхода дерева, тогда уже по кусочкам её парсить/анализировть учитывая контекст. думаю такая идея сработает.

я запретил операторы "<<" и ">>" внутри шаблонных параметров полностью продублировав логику семейства i_expr. затем там запретил всё что мешает парсить код. вот ключевой момент:

https://github.com/adler3d/unordered/blob/3dfcd153e7a5f906e0835aeee3f953a296142a94/code/uidevs_from_nout_d_temp/uidev_one/lex_gen/t_inl_parser.inl#L283

я выкинул целых два-три уровня с операторами "<<" и ">>" итд. проблемы с "T * T" и T::something не вижу.

считаю что символы для обжатия шаблонных параметров в С++ выбраны неправильно и это нужно исправлять. вот даже тему поднял когда-то:

https://gamedev.ru/flame/forum/?id=185614

я даже одному прогеру из яндекса который вроде как сидит в комитете стандартизации писал про это, но это не помогло.

я делал парсинг С++ кода без "семантические действия в генераторе" - полёт нормальный. вот исходник парсера С++11 на QapDSL v1:

https://github.com/adler3d/unordered/blob/master/code/uidevs_from_nout_d_temp/uidev_one/lex_gen/t_inl_parser.inl

делался вот для это темы:https://gamedev.ru/flame/forum/?id=172020

CLL в ISPA — переносимый язык семантических действий для генераторов парсеров. Объявление переменных, условий и циклов, генерация AST и кода на C++ без привязки к языку парсера. Пример, разбор и сравнение с ANTLR, Bison.

CLL - даже не гуглиться по хорошему. Сухо написано. Но я не знаю как написать лучше.

Заниматься семантическим анализом на стороне DSL - это такой вид мазохизма, как мне кажется. Считаю что всё это надо делать строго на стороне С++ и никак иначе. Может конечно и передумаю, но пока так.

ваш генератор по описанию не плох как я упоминал для JSON, XML

с XML у меня проблемы - там есть тэги которые не закрываются из-за дизайна/ошибок, это приводит к тому что максимум что можно - это разбить всё на токены/плоские_лексемы. дерево построить очень сильно как не всегда получается. с JSON дела получше, но скорость парсинга пока конечно не такая крутая как у заточенных под это дело монстров подобных rapidjson.

я бы либо писал парсер вручную либо использовал подобный вашему генератор

а почему не своим собственным генератором? у вас же всё должно быть круто с производительностью, не понимаю.

мы кстати можем работать над этим генератором вместе. Мне хотелось бы найти команду т.к проект стает достаточно большим для реализации в одиночку.

интересно, но я не представляю чем могу помочь. показать как круто можно обходить строго типизированное AST? это можно посмотреть прямо сейчас в моих исходниках. могу даже дать прямую ссылку на QapGen:

https://github.com/adler3d/QapGen/blob/33349ad9b14e3230e8bd7dab0df7174f4aa9fcb5/src/QapGenV2025/QapGen/visitor_gen.h#L344

вот от этой строки и дальше вся круто-та: посетители/строители/интерпретаторы в одном месте строят интерпретируют и посещают строго типизированное дерево. с небольшими костылями(для нескольких разнотипных обходов), но всё работает.

описывать ваш DSL на QapDSL v2 вам не очень интересно, как я понял. что ещё остаётся?

что обычно или структура или прямой тип данных

ну тогда ваш генератор парсеров пока круче моего по количеству фич и мало в чём ему уступает, если вообще уступает хоть в чём-то.

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

плохо понимаю что у вас происходит в коде, но я попробовал перевести некоторые штуки на QapDSL v2:

t_group:i_val{
  '('
  vector<t_member> members;
  ')'
  t_ast_node make_ast_node(){return ast_node(members);}
}
t_keyvalue:i_prefix{...}
t_value:i_prefix{...}
emit_polymorphic_type_foreach_pice(
  arr=split("name | CSEQUENCE | STRING | HEX | BIN | NOSPACE | ESCAPED | DOT | OP | LINEAR_COMMENT | cll"," | "),
  parent="i_val"
);
t_member{
  TAutoPtr<i_prefix> prefix?;
  " "?
  TAutoPtr<i_val> val;
  " "?
  t_quantifier quantifier?;
  " "?
  t_ast_node make_ast_node(){return ast_node({make_ast_node(prefix),make_ast_node(val),make_ast_node(quantifier)});}
}

не знаю насколько правильно я понял как у вас храниться AST.

крутой у вас генератор, таблицы переходов, круто всё. я до такой оптимизации ещё не дошёл. у меня пока генератор простой и со своей грамматикой справляется за 143.02 ms. выхлоп 5700 строк С++ кода(197kb). вот он:

https://github.com/adler3d/QapGen/blob/master/src/QapGenV2025/QapGen/meta_lexer.inl

а вот исходник(749 строк и 12.4кб):

https://github.com/adler3d/QapGen/blob/22004f97ba6136d85ad4a13dcd099402311fe1af/src/QapGenV2025/Release/input.qapdsl.hpp#L270

Information

Rating
842-nd
Location
Дзержинск, Нижегородская обл., Россия
Registered
Activity

Specialization

Software Developer, Game Developer
From 231,456 ₽
C++
Node.js
JavaScript