Як написати інтерпретатор / аналізатор команд?


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.


Я зовсім не бачу, як це стосується конкретно PHP. У цій темі інтерпретатора / компілятора вже є багато тем для SO та SE.
Раффаел

3
Ніхто не згадував getopt?
Антон Барковський

@AntonBarkovsky: Я так і зробив. Дивіться мої посилання. Я думаю, що такі відповіді, як Уберменш, є лише надто складними для того, що намагається зробити ОП.
квентин-зірин

1
Я також цитував простий підхід, використовуючи RegExp. Відповідь також оновлена
Ubermensch

Не згадав жодного конкретного прогр. язик. Ви можете додати тег "c", тег "ruby", "php", можливо, є "open source lib"., стандартний lib. для вашого прогр. язик.
umlcat

Відповіді:


14

Дозвольте, відверто визнаю, створення аналізатора - це копітка робота, яка наближається до технології компілятора, але побудова такої виявиться гарною пригодою. І приходить аналізатор з перекладачем. Отже, ви повинні побудувати і те, і інше.

Швидке знайомство з аналізаторами та перекладачами

Це не надто технічно. Тож експерти не злякаються на мене.

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

Лекс і Якк - це канонічні форми для створення лексерів і парсерів на основі граматики BNF під моментом C, і це рекомендований варіант. Більшість парсерів - це клон Лекса та Якка.

Етапи побудови аналізатора / інтерпретатора

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

Тож у вищенаведеному випадку вашими доданими маркерами будуть будь-які цифри та знак плюс із визначенням того, що робити зі знаком плюс у лексемі

Примітки та поради

  • Виберіть техніку аналізатора, яка оцінюється зліва направо LALR
  • Прочитайте цю книгу-дракона на « Компілятори», щоб відчути це. Я особисто не закінчив книгу
  • Це посилання дасть надшвидке розуміння Lex та Yacc під Python

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

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

Зазвичай цей підхід може бути легко вирішений. Мінус полягає в тому, що у вас не може бути багато вкладених виразів з чітким синтаксисом, і ви не можете мати простих функцій вищого порядку.


2
Це один із найважчих можливих способів. Розділення пропусків лексики та розбору тощо - це, мабуть, корисно для реалізації синтаксичного аналізатора для дуже складної, але архаїчної мови. У сучасному світі без лексерів розбір є найпростішим варіантом за замовчуванням. Комбінатори парсингу або eDSL простіші у використанні, ніж спеціальні препроцесори, такі як Yacc.
SK-логіка

Я погодився з логікою SK, але оскільки потрібна загальна детальна відповідь, я запропонував Lex та Yacc та деякі основні параметри аналізу. getopts, запропонований Антоном, також є більш простим варіантом.
Ubermensch

ось що я вже сказав - lex and yacc - один із найскладніших способів розбору, і навіть не загальний. Розбір без лексерів (наприклад, пакрат або простий Парсек) набагато простіший для загального випадку. І книга Дракона вже не дуже корисна вступ до розбору - вона надто застаріла.
SK-логіка

@ SK-логіка Ви можете порекомендувати кращу оновлену книгу. Здається, вона охоплює всі основи для людини, яка намагається зрозуміти розбір (принаймні, на моє сприйняття). Що стосується lex та yacc, то хоч і важко, але він широко використовується, і багато мов програмування забезпечують його реалізацію.
Ubermensch

1
@ alfa64: обов'язково повідомте нам про це, коли ви насправді
кодуєте

7

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

По-друге, оскільки ви використовуєте загальноприйнятий стандарт, не вигадуйте колесо. Використовуйте наявну бібліотеку, щоб зробити це за вас. Якщо ви використовуєте аргументи стилю GNU, то напевно вже існує зріла бібліотека на вашій мові на вибір. Наприклад: c # , php , c .

Хороший варіант розбору бібліотеки навіть надрукує допомогу у форматі на доступні для вас опції.

РЕДАКЦІЯ 12/27

Здається, ви робите це складніше, ніж це є.

Коли ви дивитесь на командний рядок, це дійсно досить просто. Це лише варіанти та аргументи до цих варіантів. Складних питань дуже мало. Опція може мати псевдоніми. Аргументи можуть бути списками аргументів.

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

Якщо ми говоримо про GNU, будь-який варіант може мати лише довгу форму та коротку форму (один символ) як псевдоніми. Будь-які аргументи, що містять пробіл, повинні бути оточені лапками. Кілька варіантів короткої форми можуть бути ланцюговими. Варіант (и) з короткою формою повинен продовжуватися одним тире, довгою формою двома тиреми. Тільки останні з прикованих варіантів короткої форми можуть мати аргумент.

Все дуже прямо. Все дуже поширене. Також впроваджено на будь-якій мові, яку ви можете знайти, ймовірно, п'ять разів.

Не пиши це. Використовуйте те, що вже написано.

Якщо у вас є щось на увазі, крім стандартних аргументів командного рядка, просто скористайтеся однією з МНОГО вже існуючих перевірених бібліотек, які це роблять.

У чому ускладнення?


3
Завжди завжди використовуйте спільноту з відкритим кодом.
Спенсер Ратбун

ви пробували getoptionkit?
alfa64

Ні, я не працював у php протягом декількох років. Цілком можуть бути і інші бібліотеки PHP. Я використав бібліотеку аналізатора командного рядка c #, до якої я пов’язаний.
квентин-зірин

4

Ви вже пробували щось на зразок http://qntm.org/loco ? Такий підхід набагато чіткіший, ніж будь-який спеціальний рукописний текст, але не потребує окремого інструменту генерації коду, як Lemon.

EDIT: І загальним прийомом для обробки командних рядків зі складним синтаксисом є об'єднання аргументів назад у єдиний розділений пробілом пробіл, а потім їх належним чином розібрати, як ніби це вираження якоїсь конкретної доменної мови.


+1 приємне посилання, мені цікаво, чи доступний він на Github чи щось інше. А як щодо умов використання?
хакре

1

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

Схоже, це може бути схоже на синтаксис PHP. Якщо так, PHP поставляється з аналізатором, ви можете повторно використовувати, а потім перевірити більш конкретно. Нарешті, вам потрібно розібратися з лексемами, але схоже, що це просто зліва направо, так що насправді це лише ітерація над усіма лексемами.

Деякі приклади повторного використання аналізатора маркера PHP ( token_get_all) наведені у відповідях на наступні запитання:

Обидва приклади містять і простий парсер, ймовірно, щось подібне до вашого сценарію.


так, я поспішив граматичні речі, я додам зараз.
alfa64

1

Якщо ваші потреби прості, і ви обидва маєте час і цікавитесь цим, я піду тут проти зерна і скажу, що не соромтеся писати власний парсер. Це хороший досвід навчання, якщо нічого іншого. Якщо у вас є складніші вимоги - вкладені виклики функцій, масиви тощо - просто пам’ятайте, що це може зайняти гарний шматок часу. Одним із важливих позитивних результатів прокатування є те, що не повинно виникнути питання інтеграції до вашої системи. Мінус - це, звичайно, всі гвинти вини.

Працюйте проти жетонів, але не використовуйте жорстко кодовані команди. Тоді проблема з подібними звуковими командами згасає.

Усі завжди рекомендують книгу-дракона, але я завжди вважав «Написання укладачів та перекладачів» Рональда Мака кращим вступником.


0

Я написав програми, які працюють так. Один був ботом IRC, який має подібний синтаксис команд. Є величезний файл який є великим твердженням перемикача. Це працює - працює швидко - але це дещо складно підтримувати.

Іншим варіантом, який має більше спільноти OOP, є використання обробників подій. Ви створюєте масив key-value з командами та їх виділеними функціями. Коли команда задана, ви перевіряєте, чи має масив даний ключ. Якщо це так, викликайте функцію. Це було б моєю рекомендацією щодо нового коду.


я читав ваш код, і це точно така ж схема, як і мій код, але, як я зазначив, якщо ви хочете, щоб інші користувалися, вам потрібно додати перевірку помилок та інше
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 сказано: «Іронія - це набір для розробки мов для впровадження мов на платформі .NET».

Дивіться офіційну домашню сторінку «Іронії» на CodePlex: Irony - .NET Language Kit Kit .


Як би ви використовували його з PHP?
SK-логіка

Я не бачу жодного тегу PHP або посилання на PHP у питанні.
Олів'є Якот-Дескомб

Я бачу, це було раніше про PHP, але зараз переписано.
SK-логіка

0

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

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


0

Чому ви трохи не спрощуєте свої вимоги?

Не використовуйте повний аналізатор, його занадто складний і навіть непотрібний для вашого випадку.

Зробіть цикл, напишіть повідомлення, яке представляє вас "підказка", може бути поточним шляхом, на якому ви знаходитесь.

Зачекайте рядок, "розберіть" рядок і зробіть щось залежно від вмісту рядка.

Рядок може "розбиратися", як очікування рядка, в якому пробіли є роздільниками ("токенізатором"), а решта символів згруповані.

Приклад.

Програма виводить (і залишається в одному рядку): / user / files / Користувач пише (у тому ж рядку) список усіх;

Ваша програма генерує подібний список, колекцію чи масив

list

all;

або, якщо ";" вважається роздільником як пробіли

/user/files/

list

all

Ваша програма може розпочатися, очікуючи однієї інструкції, без "дудків" у стилі unix, а також перенаправлення у стилі вікон.

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

Шаблон дизайну команд застосовується до вашого випадку:

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".


0

перед вами кілька завдань.

дивлячись на ваші вимоги ...

  • Вам потрібно розібрати команду. Це досить легке завдання
  • Потрібно мати розширювану мову команд.
  • Потрібно мати перевірку помилок та пропозиції.

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

Помилка перевірки означає, що ваша система також знає про можливі команди. Це було б частиною системи після командування.

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


0

У функціональному програмуванні є приємна особливість, яку ви могли б зацікавити.

Це називається узгодженням шаблону .

Ось два посилання для деякого прикладу відповідності шаблонів у Scala та F # .

Я погоджуюсь з вами, що використання switchструктур є трохи стомлюючим, і мені особливо сподобалося використання патерн-відповідності, що реалізує компілятор у Scala.

Зокрема, я рекомендую вам переглянути приклад обчислення лямбда на веб-сайті Scala.

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


0

Ознайомтеся з Apache CLI , все це, здається, робить саме те, що ви хочете зробити, тому навіть якщо ви не можете використовувати його, ви можете перевірити його архітектуру та скопіювати це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.