Как написать интерпретатор команд / парсер?

22

Проблема: Запустите команды в виде строки.

  • пример команды:

    /user/files/ list all; эквивалентно: /user/files/ ls -la;

  • другой:

    post tw fb "HOW DO YOU STOP THE TICKLE MONSTER?;"

эквивалентно: post -tf "HOW DO YOU STOP THE TICKLE MONSTER?;"

Текущее решение:

tokenize string(string, array);

switch(first item in array) {
    case "command":
        if ( argument1 > stuff) {
           // do the actual work;
        }
}

Проблемы, которые я вижу в этом решении:

  • Нет проверки ошибок, кроме вложенных ifs-else внутри каждого случая. Сценарий становится очень большим и сложным в обслуживании.
  • Команды и ответы жестко закодированы.
  • Нет способа узнать, являются ли флаги правильными или отсутствуют параметры.
  • Недостаток интеллекта, чтобы предположить, что «вы, возможно, хотели запустить $ command».

И последнее, на что я не могу ответить - это синонимы в разных кодировках, например:

case command:
case command_in_hebrew:
    do stuff;
break;

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

В настоящее время я программирую это на PHP, но может сделать это на PERL.

alfa64
источник
Я вообще не вижу, как это относится конкретно к PHP. В этой теме интерпретатора / компилятора уже много тем для SO и SE.
Рафаэль
3
Никто не упомянул getopt?
Антон Барковский
@AntonBarkovsky: я сделал. Смотрите мои ссылки. Я думаю, что ответы типа Ubermensch просто слишком сложны для того, что пытается сделать OP.
Квентин Старин
1
Я также процитировал простой подход с использованием RegExp. Ответ также обновляется
Ubermensch
Не упомянул какой-либо конкретный прогр. яз. Вы можете добавить тег "c", тег "ruby", тег "php", возможно, есть библиотека с открытым исходным кодом, стандартная библиотека или "обычно используемая, но еще не стандартная библиотека". для вашего прогр. яз.
umlcat

Ответы:

14

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

Краткое введение в парсер и интерпретаторы

Это не слишком технично. Так что эксперты не раздражаются на меня.

Когда вы вводите некоторый вход в терминал, терминал разделяет вход на несколько единиц. Вход называется выражением, а несколько единиц называются токенами. Эти токены могут быть операторами или символами. Поэтому, если вы введете 4 + 5 в калькуляторе, это выражение будет разбито на три токена 4, +, 5. Плюс считается оператором при 4 и 5 символах. Это передается программе (рассматриваемой как интерпретатор), которая содержит определение для операторов. Основываясь на определении (в нашем случае add), он добавляет два символа и возвращает результат в терминал. Все компиляторы основаны на этой технологии. Программа, которая разбивает выражение на несколько токенов, называется лексером, а программа, которая преобразует эти токены в теги для дальнейшей обработки и выполнения, называется парсером.

Lex и Yacc - это канонические формы для построения лексеров и синтаксических анализаторов, основанные на грамматике BNF под C, и это рекомендуемый вариант. Большинство парсеров - это клоны Лекса и Яка.

Шаги в построении парсера / интерпретатора

  1. Классифицируйте свои токены по символам, операторам и ключевым словам (ключевые слова являются операторами)
  2. Создайте свою грамматику, используя форму BNF
  3. Напишите функции парсера для ваших операций
  4. Скомпилируйте его как программу

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

Примечания и советы

  • Выберите технику синтаксического анализа, которая оценивает слева направо LALR
  • Прочтите эту книгу о компиляторах, чтобы почувствовать ее. Я лично не закончил книгу
  • Эта ссылка дала бы сверхбыструю информацию о Lex и Yacc под Python

Простой подход

Если вам нужен простой механизм синтаксического анализа с ограниченными функциями, превратите ваше требование в регулярное выражение и просто создайте целый набор функций. Для иллюстрации предположим простой синтаксический анализатор для четырех арифметических функций. Таким образом, вы будете сначала вызывать оператор, а затем список функций (похожих на lisp) в стиле, (+ 4 5)или же (add [4,5])вы можете использовать простой RegExp для получения списка операторов и символов, с которыми нужно работать.

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

Ubermensch
источник
2
Это один из самых сложных способов. Разделение проходов лексирования и парсинга и т. Д. - это, вероятно, полезно для реализации высокопроизводительного синтаксического анализатора для очень сложного, но архаичного языка. В современном мире анализ без лексеров - самый простой вариант по умолчанию. Синтаксические анализаторы или eDSL проще в использовании, чем специализированные препроцессоры, такие как Yacc.
SK-logic
Согласен с SK-logic, но поскольку требуется общий подробный ответ, я предложил Lex и Yacc и некоторые основы синтаксического анализа. getopts, предложенный Антоном, также является более простым вариантом.
Ubermensch
вот что я сказал - lex и yacc - один из самых сложных способов анализа, и даже не достаточно универсальный. Разбор без лексеров (например, packrat или простой Parsec-подобный) намного проще для общего случая. И книга Дракона уже не очень полезна для разбора - она ​​слишком устарела.
SK-logic
@ SK-logic Можете ли вы порекомендовать более обновленную книгу. Кажется, что он охватывает все основы для человека, пытающегося понять синтаксический анализ (по крайней мере, в моем восприятии). Что касается lex и yacc, хотя и сложный, он широко используется, и многие языки программирования обеспечивают его реализацию.
Ubermensch
1
@ alfa64: обязательно сообщите нам, когда вы на самом деле
закодируете
7

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

Во-вторых, поскольку вы используете принятый стандарт, не изобретайте велосипед. Используйте существующую библиотеку, чтобы сделать это для вас. Если вы используете аргументы стиля GNU, почти наверняка уже есть зрелая библиотека на вашем языке. Например: c # , php , c .

Хорошая библиотека для разбора опций даже напечатает отформатированную справку о доступных для вас опциях.

РЕДАКТИРОВАТЬ 12/27

Кажется, вы делаете это более сложным, чем оно есть.

Когда вы смотрите на командную строку, это действительно довольно просто. Это просто параметры и аргументы для этих параметров. Сложных вопросов очень мало. Вариант может иметь псевдонимы. Аргументы могут быть списками аргументов.

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

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

Все очень просто. Все очень часто. Также был реализован на каждом языке, который вы можете найти, вероятно, в пять раз.

Не пиши это. Используйте то, что уже написано.

Если вы не имеете в виду что-то кроме стандартных аргументов командной строки, просто используйте одну из МНОГИХ уже существующих, протестированных библиотек, которые делают это.

Что за осложнение?

Квентин-starin
источник
3
Всегда, всегда используйте сообщество с открытым исходным кодом.
Спенсер Рэтбун
ты пробовал getoptionkit?
alfa64
Нет, я не работал в php уже несколько лет. Вполне могут быть и другие библиотеки php. Я использовал библиотеку синтаксического анализатора командной строки c #, с которой я связан.
Квентин-старин
4

Вы уже пробовали что-то вроде http://qntm.org/loco ? Этот подход намного чище любого рукописного ad hoc, но не требует отдельного инструмента для генерации кода, такого как Lemon.

РЕДАКТИРОВАТЬ: И общая хитрость для обработки командных строк со сложным синтаксисом состоит в том, чтобы объединить аргументы обратно в одну строку, разделенную пробелами, а затем правильно проанализировать ее, как если бы она была выражением некоторого предметно-ориентированного языка.

SK-логика
источник
+1 хорошая ссылка, интересно, есть ли она на github или что-то еще. А как насчет условий использования?
Хакре
1

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

Похоже, что это может быть похоже на синтаксис PHP. Если это так, PHP поставляется с анализатором, вы можете повторно использовать, а затем проверить более конкретно. Наконец, вам нужно разобраться с токенами, но похоже, что это просто слева направо, так что на самом деле это просто итерация по всем токенам.

Некоторые примеры повторного использования PHP-токена parser ( token_get_all) приведены в ответах на следующие вопросы:

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

hakre
источник
да, я бросил грамматику, сейчас добавлю.
alfa64
1

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

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

Все всегда рекомендуют книгу о драконах, но я всегда находил, что «Написание компиляторов и интерпретаторов» Рональда Мака - лучшее введение.

GrandmasterB
источник
0

Я написал программы, которые работают так. Один из них был IRC-ботом с похожим командным синтаксисом. Есть огромный файл который является большим оператором переключения. Это работает - это работает быстро - но это довольно сложно поддерживать.

Другой вариант, который имеет больше вращения ООП, заключается в использовании обработчиков событий. Вы создаете массив ключ-значение с командами и их выделенными функциями. Когда дается команда, вы проверяете, есть ли у массива заданный ключ. Если это так, вызовите функцию. Это было бы моей рекомендацией для нового кода.

разбойник
источник
Я прочитал ваш код, и он точно такой же схемы, что и мой код, но, как я уже сказал, если вы хотите, чтобы другие люди использовали, вам нужно добавить проверку ошибок и прочее
alfa64
1
@ alfa64 Пожалуйста, добавьте любые пояснения к вопросу вместо комментариев. Не совсем понятно, что именно вы просите, хотя несколько ясно, что вы ищете что-то действительно конкретное. Если так, расскажите нам точно, что это такое. Я не думаю, что это очень легко перейти I think my implementation is very crude and faultyк but as i stated, if you want other people to use, you need to add error checking and stuff... Расскажите нам точно, что грубо об этом и что неисправно, это поможет вам получить лучшие ответы.
Яннис
конечно, я переделаю вопрос
alfa64
0

Я предлагаю использовать инструмент, а не реализовывать компилятор или интерпретатор самостоятельно. Ирония использует C # для выражения грамматики целевого языка (грамматика вашей командной строки). Описание CodePlex гласит: «Irony - это набор разработчика для реализации языков на платформе .NET».

Смотрите официальную домашнюю страницу Irony на CodePlex: Irony - .NET Language реализации Kit .

Оливье Жако-Дескомб
источник
Как бы вы использовали его с PHP?
SK-logic
Я не вижу тега PHP или ссылки на PHP в этом вопросе.
Оливье Жако-Дескомб
Я вижу, раньше он был про PHP, но теперь переписан.
SK-logic
0

Мой совет будет Google для библиотеки, которая решает вашу проблему.

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

ming_codes
источник
0

Почему бы вам немного не упростить ваши требования?

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

Сделайте цикл, напишите сообщение, которое представляет собой «подсказку», может быть текущим путем, которым вы являетесь.

Дождитесь строки, «проанализируйте» строку и сделайте что-нибудь в зависимости от содержимого строки.

Строка может «анализироваться», как ожидание строки, в которой пробелы являются разделителями («tokenizer»), а остальные символы сгруппированы.

Пример.

Программа выводит (и остается в одной строке): / user / files / Пользователь записывает (в одной строке) список всех;

Ваша программа сгенерирует список, коллекцию или массив как

list

all;

или если ";" считается разделителем, как пространства

/user/files/

list

all

Ваша программа может начаться с ожидаемой одной инструкции без «каналов» в стиле unix и без перенаправления в стиле windowze.

Ваша программа может составить словарь инструкций, каждая инструкция может иметь список параметров.

Шаблон проектирования команд относится к вашему случаю:

http://en.wikipedia.org/wiki/Command_pattern

Этот псевдокод "обычного с", не проверен или не закончен, просто идея того, как можно сделать.

Вы также можете сделать его более объектно-ориентированным, и на языке программирования вам понравится.

Пример:


// "global function" pointer type declaration
typedef
  void (*ActionProc) ();

struct Command
{
  char[512] Identifier;
  ActionProc Action; 
};

// global var declarations

list<char*> CommandList = new list<char*>();
list<char*> Tokens = new list<char*>();

void Action_ListDirectory()
{
  // code to list directory
} // Action_ListDirectory()

void Action_ChangeDirectory()
{
  // code to change directory
} // Action_ChangeDirectory()

void Action_CreateDirectory()
{
  // code to create new directory
} // Action_CreateDirectory()

void PrepareCommandList()
{
  CommandList->Add("ls", &Action_ListDirectory);
  CommandList->Add("cd", &Action_ChangeDirectory);
  CommandList->Add("mkdir", &Action_CreateDirectory);

  // register more commands
} // void PrepareCommandList()

void interpret(char* args, int *ArgIndex)
{
  char* Separator = " ";
  Tokens = YourSeparateInTokensFunction(args, Separator);

  // "LocateCommand" may be case sensitive
  int AIndex = LocateCommand(CommandList, args[ArgIndex]);
  if (AIndex >= 0)
  {
    // the command

    move to the next parameter
    *ArgIndex = (*ArgIndex + 1);

    // obtain already registered command
    Command = CommandList[AIndex];

    // execute action
    Command.Action();
  }
  else
  {
    puts("some kind of command not found error, or, error syntax");
  }  
} // void interpret()

void main(...)
{
  bool CanContinue = false;
  char* Prompt = "c\:>";

  char Buffer[512];

  // which command line parameter string is been processed
  int ArgsIndex = 0;

  PrepareCommandList();

  do
  {
    // display "prompt"
    puts(Prompt);
    // wait for user input
      fgets(Buffer, sizeof(Buffer), stdin);

    interpret(buffer, &ArgsIndex);

  } while (CanContinue);

} // void main()

Вы не упомянули свой язык программирования. Также можно упомянуть любой язык программирования, но желательно «XYZ».

umlcat
источник
0

у вас есть несколько задач перед вами.

глядя на ваши требования ...

  • Вам нужно разобрать команду. Это довольно простая задача
  • Вам нужно иметь расширяемый командный язык.
  • Вы должны иметь проверку ошибок и предложения.

Расширяемый командный язык указывает, что требуется DSL. Я бы посоветовал не кататься самостоятельно, а использовать JSON, если ваши расширения просты. Если они сложны, синтаксис s-выражения хорош.

Проверка ошибок подразумевает, что ваша система также знает о возможных командах. Это будет частью посткомандной системы.

Если бы я реализовывал такую ​​систему с нуля, я бы использовал Common Lisp с урезанным читателем. Каждый токен команды будет отображаться в символ, который будет указан в RC-файле s-выражения. После токенизации он будет оцениваться / расширяться в ограниченном контексте, фиксируя ошибки, и любые распознаваемые шаблоны ошибок будут возвращать предложения. После этого фактическая команда будет отправлена ​​в ОС.

Пол Натан
источник
0

В функциональном программировании есть хорошая функция, на которую вам может быть интересно посмотреть.

Это называется сопоставлением с образцом .

Вот две ссылки для некоторого примера сопоставления с образцом в Scala и в F # .

Я согласен с вами в том, что использование switchструктур немного утомительно, и мне особенно понравилось использовать паттерн соответствия во время реализации компилятора в Scala.

В частности, я бы порекомендовал вам ознакомиться с примером лямбда-исчисления на веб-сайте Scala.

Это, на мой взгляд, самый разумный способ продолжить, но если вам нужно строго придерживаться PHP, то вы застряли в «старой школе» switch.

SRKX
источник
0

Проверьте Apache CLI , кажется, что вся его цель - делать именно то, что вы хотите, поэтому даже если вы не можете использовать его, вы можете проверить его архитектуру и скопировать его.

Стивен Рудольф
источник