Самая чистая структура данных для использования при интерпретации данных из аккуратно структурированных пользовательских команд (на C++)

Я хотел бы написать простую внутреннюю программу, которая анализирует пользовательские команды, написанные на языке собственного изобретения нашей команды (но основанном на другой программе, с которой мы уже знакомы). Анализатор команд, над которым я сейчас работаю, будет просто пользовательским интерфейсом, через который пользователь сможет запускать другие алгоритмы, которые я уже написал. (Кстати, эти другие алгоритмы используются для создания входных файлов для пакета молекулярно-динамического моделирования под названием LAMMPS. .) Единственное, что мне действительно осталось сделать, это просто написать этот пользовательский интерфейс, но, как оказалось, написание собственного языка сценариев — почти неразрешимая задача для не-разработчика программного обеспечения, с которой он может справиться самостоятельно.

Согласно полученным мной ответам, то, что я пытаюсь сделать, будет считаться языком домена, и не рекомендуется пытаться сделать свой собственный DSL из-за огромного количества работы, необходимой для того, чтобы сделать его полезным и свободным от ошибок.

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

Для этого я, скорее всего, буду использовать Lua, потому что он кажется наиболее подходящим для наших нужд. Таким образом, на данный момент остальная часть этого вопроса больше не актуальна, поскольку ответ будет таким: «Не делайте этого сами». Но я все равно оставлю часть этого здесь, чтобы другие пользователи могли читать и учиться на замечательных ответах ниже.

Еще раз спасибо всем, кто ответил!


Старый вопрос:

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

Проблема в том, что в настоящее время я использую операторы if для анализа строк. Это чрезвычайно неудобный способ разбора команд, потому что даже для коротких команд из 3 слов код взрывается на вложенные ifs в 3 слоя. Таким образом, более длинные предложения из 8+ слов станут вложенными, если будут иметь глубину более 8 слоев.

Такой подход к программированию быстро становится неуправляемым, особенно когда мне нужно внести какие-либо существенные изменения в команду.


Мой вопрос заключается в том, существует ли структура данных в C++, которая может помочь мне лучше управлять моими гигантскими вложенными операторами if, или может ли кто-нибудь предложить лучший способ анализа строки для множества различных типов данных (т. float) и выводить сообщение об ошибке, когда ожидаемый тип не найден?


Вот пример короткого пользовательского сеанса, показывающий типы команд, которые я хотел бы интерпретировать:

    load "Basis.Silicon" as material 1
    add material 1 to layer 1
    rotate layer 1 about x-axis by 45 degrees
    translate layer 1 in x-axis by 10 nm
    generate crystal

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

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

Спасибо всем!


person Vladimir    schedule 14.08.2014    source источник
comment
Мы не знаем, какую грамматику вы хотите поддерживать. Существует целый спектр способов интерпретации текста, от простейшего набора правил общепринятых предложений до сложных иерархических грамматических структур. В его нынешнем состоянии на этот вопрос нельзя ответить.   -  person Karoly Horvath    schedule 15.08.2014
comment
Будьте готовы работать много месяцев. Вам понадобятся многие тысячи дополнительных строк исходного кода!   -  person Basile Starynkevitch    schedule 15.08.2014
comment
@BasileStarynkevitch Ваш ответ был обстоятельным и потрясающим! Спасибо за всю информацию. Я думаю, что часть проблемы с моим вопросом заключается в том, что, поскольку я только начинаю, у меня нет четкого понимания (пока) того, как и что мне вообще нужно спрашивать. Ваши ресурсы, тем не менее, должны помочь мне в правильном направлении, чтобы преодолеть этот первоначальный горб. Если бы я мог, я бы удвоил ваш голос, но, увы, я могу отдать только один голос. (Так что вместо этого я просто проголосовал за все ваши комментарии ;))   -  person Vladimir    schedule 15.08.2014
comment
Используйте string::npos вместо -1   -  person Neil Kirk    schedule 15.08.2014
comment
@NeilKirk Подойдет! Спасибо :)   -  person Vladimir    schedule 15.08.2014
comment
Используйте std::map или поиск по таблице. Используйте <keyword, function pointer> записи.   -  person Thomas Matthews    schedule 15.08.2014
comment
@ThomasMatthews Да, я мог бы объединить это с указателями функций, которые вы упомянули в своем посте. Это действительно должно помочь! Большое спасибо! Мне на самом деле очень грустно, я не могу поставить больше одной галочки... :(   -  person Vladimir    schedule 15.08.2014
comment
@BasileStarynkevitch Я думал о том, что мы обсуждали, и наконец понял, как задать свой вопрос. Так что я просто полностью переписал его, и я надеюсь, что эта версия теперь прояснит все серые области, которые были в старой версии.   -  person Vladimir    schedule 15.08.2014
comment
@πάνταῥεῖ Я только что переписал свой вопрос, стараясь быть максимально ясным и конкретным. Пожалуйста, взгляните на него, если у вас есть возможность, и оцените соответственно. Спасибо!   -  person Vladimir    schedule 15.08.2014
comment
Вы должны объяснить, что в настоящее время делает ваше приложение C++? Он управляет 3D-принтером?   -  person Basile Starynkevitch    schedule 15.08.2014
comment
@BasileStarynkevitch О нет, это не так уж и причудливо. Он просто хранит связанный список элементарных кристаллических ячеек, с которыми пользователь может взаимодействовать и манипулировать через командную строку. У меня есть другая программа (которую я уже написал), которая берет эти элементарные ячейки и выращивает из них полный кристалл, который затем используется в LAMMPS (программа, написанная Sandia National Laboratories), которая работает на суперкомпьютере нашего университета и выполняет моделирование молекулярной динамики. Но все эти сверхмощные алгоритмы уже написаны, и сейчас я просто работаю над пользовательским интерфейсом. :)   -  person Vladimir    schedule 15.08.2014


Ответы (3)


Ваш вопрос не ясен. И ваши цели сложнее, чем то, во что вы верите.

Либо вы считаете, что хотите как-то обрабатывать предложения на человеческом языке (например, на английском). Затем вы хотите изучить обработку естественного языка, и вы можете найти несколько библиотек, связанных с этой областью.

Или вы считаете, что хотите интерпретировать какой-то формальный язык программирования или скриптов. Затем вы хотите изучить интерпретаторы и компиляторы. Кстати, в этом случае вы можете просто встроить существующий интерпретатор (например, Lua, Guile, Python и т. д. ...) в вашей программе.

Вы также можете представить себе экспертные системы с база знаний, состоящая из правил (этот подход можно рассматривать как нечто среднее между НЛП и языком сценариев). ">механизм вывода (возможно, CLIPS). См. также блог Дж. Питрата.

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

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

Если вы пойдете по пути встроенного интерпретатора: изучите интерпретаторы, о которых я упоминал (Guile, Lua, Python, Neko и т. д.). ) и выберите, какой из них вы хотите встроить.

Если по какой-либо причине вы хотите создать интерпретатор с нуля: сначала изучите несколько языков программирования (включая языки сценариев, такие как Ruby, Python, Ocaml, Scheme, Lua, Neko, ...). Прочтите книги по Прагматика языка программирования (автор М. Скотт) и Маленькие фрагменты Lisp (от Queinnec). Прочтите также учебники по компиляции и разбору, а также по сборке мусора и формальным (например, денотативный) семантика. На все это может потребоваться дюжина лет работы.

Обратите внимание, что по опыту встраивание программного обеспечения в интерпретатор является очень структурным проектом. Если вы не подумали об этом в начале, вам, вероятно, потребуется много перепроектировать и реорганизовать существующее приложение. Например, при встраивании программного обеспечения в интерпретатор вы не можете допустить, чтобы неверный ввод привел к сбою программы. Таким образом, обработка ошибок и управление памятью (взаимодействие с GC интерпретатора) сложны и накладывают новые ограничения. Следовательно, вам нужно переосмыслить свое приложение.

Если все это ново (и даже если вы не выбираете, например, Guile в качестве встроенного интерпретатора): изучите и попрактикуйтесь немного в Scheme - например. с Guile или PltScheme- (например, прочитав SICP), прочитайте немного о λ-исчисление и замыкания, затем прочитайте книгу Queinnec Lisp In Small Pieces. Вспомните проблему с остановкой (отчасти поэтому интерпретаторы трудно программировать).

Кстати, предлагаемый вами синтаксис (например, rotate mat 1 by x 90) не очень удобочитаем и выглядит как COBOL. Если возможно, используйте язык, который выглядит знакомым существующим. Сделайте так, чтобы его читали легко!

Начните с чтения всех вики-страниц, на которые я здесь ссылаюсь.

FWIW, я являюсь основным автором MELT, язык домена (во многом вдохновленный Scheme) для расширения компилятор GCC. Некоторые из статей/документаций, которые я написал, могут вдохновить вас (и содержать ценные ссылки).

Дополнения (после переформулировки вопроса)

Кажется, вы изобретаете какой-то формальный синтаксис, например

add material 1 to layer 1
rotate layer 1 about x-axis by 90 degrees
translate layer 1 in x-axis by 10 inches

Я не могу понять, что это за язык? Вы реализуете 3D-принтер? Если да, вам следует придерживаться какого-либо существующего стандартного формального языка в этой области.

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

Является ли этот синтаксис вашим изобретением, или есть какой-то документ, определяющий (и многие тысячи уже существующих строк, закодированных) для вашего доменного языка. Если вы просто изобретаете это, пожалуйста, пересмотрите синтаксис и семантику. Во-первых, вам нужно указать на бумаге полный синтаксис и семантику вашего DSL.

Ваш DSL по Тьюрингу завершен? (Я предполагаю, что да, потому что полнота по Тьюрингу достигается очень быстро - например, с переменными и циклами....). Если да, вы изобретаете язык сценариев. Пожалуйста, не изобретайте язык сценариев, не зная нескольких языков программирования и сценариев (затем прочитайте Программирование Прагматика языка...). Дело в том, что если ваш скриптовый язык станет успешным, продвинутые пользователи рано или поздно напишут на нем важные программы (например, многие тысячи строк). Затем эти продвинутые пользователи станут программистами. В этом случае очень важно (по социальным и экономическим причинам) иметь хорошо обоснованный и знакомый DSL (если возможно, расширение какого-либо существующего языка сценариев).

Если ваш DSL уже существует, придерживайтесь его спецификации на бумаге. Если эта спецификация недостаточно хороша, улучшите ее с помощью формализации (например, написав для нее некоторый синтаксис БНФ и некоторую формальную (например, денотационную) семантику). Опубликуйте и обсудите эту формализацию с существующими пользователями.

В нескольких отраслях появились специальные DSL, которые стали широко использоваться, но были плохо спроектированы (например, во французской атомной промышленности DSL Gibiane был разработан в 1970-х годах физиками-ядерщиками, а не учеными-компьютерщиками; американская компания Boeing ходят слухи, что корпорация совершала подобные ошибки). Тогда поддержка и улучшение многих сотен тысяч строк сценариев DSL становится кошмаром (и может означать потерю миллионов долларов или евро). Так что вам лучше придерживаться какого-нибудь существующего языка сценариев. Плюсы в том, что в нем существует некоторая культура (например, вы можете найти десятки книг по Python или Lua и многие обученные инженеры знакомы с ними), что интерпретатор широко используется и тестируется, что сообщество, работающее над ним, улучшает интерпретаторы, поэтому в нем довольно мало неисправленных ошибок.

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

В качестве контрпримера J.Ousterhout изобрел широко используемый Tcl язык сценариев с утверждением, что сценарии всегда малы (например, только сотни строк) и не разрастутся до большой базы кода ; к сожалению, некоторые из них это сделали, и Tcl известен как плохой язык для кодирования многих десятков тысяч строк (даже если Tcl — простой и удобный язык для крошечных скриптов). Мораль этой истории заключается в том, что если скриптовый язык (полный по Тьюрингу) станет успешным, какой-нибудь «сумасшедший» продвинутый пользователь напишет сотни тысяч кодов скриптов. Поэтому вам нужно, чтобы язык сценариев был хорошо разработан с самого начала. Следовательно, вам следует принять и адаптировать хороший существующий язык сценариев (и избегать изобретения незнакомого синтаксиса без хорошего знания нескольких существующих языков сценариев).

более поздние дополнения

PS: моя критика Tcl не совсем субъективна: дело в том, что Tcl был разработан для небольших скриптов (прочитайте первые статьи J.Ousterhout о Tcl), но я хочу сказать, что когда вы предлагаете язык сценариев, полный по Тьюрингу, какой-нибудь «сумасшедший» пользователь в конечном итоге напишет для него огромные сценарии. Следовательно, вам нужно предвидеть такое «сумасшедшее» использование, предлагая язык сценариев, который «расширяется» до больших сценариев, поэтому он построен в соответствии с методами разработки программного обеспечения для большой базы кода программного обеспечения.

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

person Basile Starynkevitch    schedule 14.08.2014
comment
Спасибо Василий, я понимаю, что вы имеете в виду. На самом деле я хотел бы иметь список примерно из 15 ключевых слов, наличие/порядок которых определял бы код, который будет выполняться. Так что мне не нужен процессор естественного языка. - person Vladimir; 15.08.2014
comment
Я настоятельно рекомендую встроить какой-нибудь существующий интерпретатор. И не думайте о ключевых словах, думайте о расширении существующего интерпретатора языка сценариев. - person Basile Starynkevitch; 15.08.2014
comment
Встраивание интерпретатора может отлично сработать. Вы случайно не знаете какие-нибудь легкие ресурсы по теме? - person Vladimir; 15.08.2014
comment
Спасибо! Я посмотрю на них. Какой интерпретатор, по вашему мнению, проще всего добавить в мою программу на C++? - person Vladimir; 15.08.2014
comment
Если ваша программа на C++ уже большая, будьте готовы к редизайну и рефакторингу некоторых ее частей. Встроить его в какой-нибудь существующий интерпретатор сложно. - person Basile Starynkevitch; 15.08.2014
comment
Потрясающий! Звучит как план! Спасибо за информацию и ссылки :) - person Vladimir; 15.08.2014
comment
Я понимаю, что вы имеете в виду, говоря, что программа должна быть достаточно знакома для использования другими программистами. Раньше я даже не думал об этом, потому что писал это для внутренних пользователей. Поэтому я просто планировал сделать краткий раздел справки, в котором объясняется, что делают все команды и т. Д. - person Vladimir; 15.08.2014
comment
Синтаксис, который я использую, на самом деле очень похож на другую программу, которую использует наша команда, и я просто имитировал то, с чем уже были знакомы мои коллеги. К сожалению, эта другая программа больше не поддерживается, и исходный код для нее никогда не публиковался, поэтому я не могу войти и посмотреть, как они обработали пользовательский интерфейс в своей программе. - person Vladimir; 15.08.2014
comment
Ваша точка зрения о том, чтобы придерживаться существующего языка, хорошо принята, и я уже загрузил несколько файлов для встраивания Lua в свою программу. Но я просто хотел попробовать работать, прежде чем полностью перейти на совершенно другой язык. ;) - person Vladimir; 15.08.2014
comment
Спасибо за этот отличный ответ! Это действительно помогает поставить некоторые проблемы, о которых я не думал, в перспективе! - person Vladimir; 15.08.2014
comment
Спасибо за внимание, я буду помнить о проблеме с памятью. - person Vladimir; 18.08.2014

РЕДАКТИРОВАТЬ: Чтобы быть более ясным, я хотел бы иметь краткий список ключевых слов (‹15). Порядок/наличие которого будет определять, какая функция будет запущена.

Вы можете создать небольшой механизм набора правил (например, что-то, что обрабатывает списки слов). Вы пишете этот движок/функцию один раз и просто передаете ему структуры данных.

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

person Karoly Horvath    schedule 14.08.2014
comment
Звучит здорово! Не могли бы вы показать мне действительно простой пример или указать, где я могу найти аналогичный пример? Спасибо! - person Vladimir; 15.08.2014
comment
Я не уверен, что это правильный путь. Вероятно, вы хотите, чтобы в ваших правилах были какие-то переменные... Тогда начинаются проблемы. - person Basile Starynkevitch; 15.08.2014
comment
@BasileStarynkevitch О, понятно, похоже, это тоже сложнее, чем кажется. - person Vladimir; 15.08.2014

Вы можете построить таблицу ключевых слов и указателей функций:

typedef void (*Function_Pointer)(void);

struct table_entry
{
  const char * keyword;
  Function_Pointer p_function;
};

table_entry function_table[] =
{
  {"car", Process_Car},
  {"bike", Process_Bike},
};

Найдите в таблице ключевое слово. Если ключевое слово найдено, разыменуйте указатель на функцию.
Следующий фрагмент кода выполнит функцию для обработки слова "автомобиль":

(function_table[0].p_function)();

Существует известная программа Eliza, которая анализирует предложения по ключевым словам.
Примеры можно найти по адресу: примеры Eliza C++

person Thomas Matthews    schedule 15.08.2014
comment
Ух ты! Это может быть действительно полезно! Так можно ли иметь указатель на функцию? С ума сошел. Это может действительно помочь очистить мой код! Спасибо за информацию :) - person Vladimir; 15.08.2014
comment
Между прочим, эта штука с Элизой была довольно крутой. Я только что разговаривал с ним. Но это может выйти за рамки того, что я могу написать сейчас. Хахаха Спасибо однако! - person Vladimir; 15.08.2014
comment
На самом деле, если подумать, я мог бы получить некоторые хорошие идеи из его исходного кода без необходимости реализовывать какие-либо действительно причудливые вещи. - person Vladimir; 15.08.2014