Автор Тема: Про парсер и лексер.  (Прочитано 53822 раз)

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Про парсер и лексер.
« Ответ #90 : Сентябрь 14, 2012, 04:46:34 pm »
Коль разговор скатывается к DSL (я правда так и не понял при чём тут контекстно зависимые лексемы), расскажу как я (этим летом) сделал некий аналог DSL без написания каких-либо парсеров вовсе.

Стоит задача автоматического тестирования программной системы (телефонной станции). Написан Симулятор внешнего мира. Программная система стартует, коннектится к Симулятору внешнего мира и думает, что она в настоящем "боевом режиме". Симулятор симулирует регистрацию телефонов, звонков, отправку всяких факсов и т. п.

Возникает вопрос как запрограммировать Симулятор. Сначала сделал это "в лоб". Написал пяток тестов. Каждый под 700 строчек C# кода. А тестов-то нужно несколько десятков если не сотен. Причём в будущем чего-то обязательно поменяется и надо будет править всё это хозяйство. Пришлось напрячь извилины и родить некий аналог DSL. Мозгом Симулятора сделал некую виртуальную машину, которая программируется на специальном языке DSL-ассемблера. Написание теста свелось к написанию эмиттера DSL-ассемблерного кода для той виртуальной машины.

Вот пара примеров:
public abstract class BasicCallEmitter: BasicTestEmitter
{
public Assembler.Procedure Emit ()
{
MACROS_Init();
CONNECT_Logic(L1, subscriberLogicId);
REGISTER_Phone(T1, proto1, L1);
REGISTER_Phone(T2, proto2, L1);
SEND_CallBegin(C1, T1, T2, codecs1);
WAIT_CallAck(C1);
MACROS_AfterReceiveCallAck1();
WAIT_CreateCall(C2);
WAIT_SetMedia(C2);
WAIT_Setup(C2);
MACROS_AfterReceiveSetup2();
SEND_SetMediaAck(C2, codecs2);
MACROS_AfterSendSetMediaAck2();
MACROS_Connect();
MACROS_AfterReceiveConnect1();
SLEEP(this.secondsDuration);
SEND_Terminate(C1, SIPBye);
WAIT_Terminate(C2);
WAIT_TerminatedAck(C1);
WAIT_TerminatedAck(C2);
ASSERT_StreamCount(0);
ASSERT_RecordingCount(0);
ASSERT_CallCount(0);
ASSERT_PromptCount(0);
SLEEP(1);
CONNECT_CentrexCDR(this.centrexCDR);
CHECK_CDR(T1, C1, T2, C2, SIPBye);
PASSED();
return this.GetProcedure();
}

protected virtual void MACROS_Connect ()
{
SEND_Connect(C2);
WAIT_Connect(C1);
}

protected virtual void MACROS_AfterReceiveCallAck1 ()
{
}

protected virtual void MACROS_AfterReceiveConnect1 ()
{
}

protected virtual void MACROS_AfterReceiveSetup2 ()
{
}

protected virtual void MACROS_AfterSendSetMediaAck2 ()
{
}
}
public abstract class Web_Fax: BasicTestEmitter
{
public Assembler.Procedure Emit ()
{
MACROS_Init();
CONNECT_Logic(L1, subscriberLogicId);
CONNECT_Logic(L2, servicePlatformId);
REGISTER_Phone(T1, proto1, L1);
REGISTER_Phone(T2, proto2, L1);
EXECUTE(MakeFaxTask);
WAIT_CreateCall(C1, desc);
WAIT_SetMedia(C1);
WAIT_Setup(C1);
NEW_Call(C2, L1);
COPY_ConfId(C2, C1);
COPY_Codecs(C2, C1);
COPY_Proto(C2, C1);
SEND_CallBegin(C2, desc);
WAIT_CallAck(C2);
SEND_SetMedia(C2);
SEND_CallAckProceded(C2);
WAIT_CreateCall(C3);
WAIT_SetMedia(C3);
WAIT_Setup(C3);
SEND_Progress(C3, true, true);
WAIT_SetMediaAck(C2);
COPY_Codecs(C1, C2);
SEND_SetMediaAck(C1);
WAIT_Progress(C2);
SEND_Progress(C1, true, true);
SEND_SetMediaAck(C3, defaultCodecs);
SEND_Connect(C3);
WAIT_Connect(C2);
SEND_Connect(C1);
MACROS_FaxSetMedia();
WAIT_SendFax();
WAIT_Terminate(C1);
WAIT_TerminatedAck(C1);
SEND_Terminate(C2, SIPBye);
WAIT_Terminate(C3);
WAIT_TerminatedAck(C2);
WAIT_TerminatedAck(C3);
ASSERT_StreamCount(0);
ASSERT_RecordingCount(0);
ASSERT_CallCount(0);
ASSERT_PromptCount(0);
SLEEP(1);
CONNECT_CentrexCDR(this.centrexCDR);
CHECK_CDR(T1, C1, T2, C2, SIPBye);
PASSED();
return this.GetProcedure();
}

public abstract void MACROS_FaxSetMedia ();
}
Эмиттеры DSL-ассемблерного кода конкретных тестов просто наследуются от этих абстрактных классов и переопределяются нужные виртуальные процедуры MACROS_***.

Вот, например, эмиттер кода теста базового звонка SIP-SIP (при написании "в лоб" было 728 строчек на C#, а теперь всего пять DSL-ассемблерных инструкций):
public class SIP_SIP_Emitter: BasicCallEmitter
{
public SIP_SIP_Emitter ()
{
reference = "SIP-SIP";
description = "Basic call test SIP-SIP";
}

protected override void MACROS_AfterReceiveCallAck1 ()
{
SEND_SetMedia(C1);
SEND_CallAckProceded(C1);
}

protected override void MACROS_AfterReceiveSetup2 ()
{
SEND_Progress(C2, true, true);
WAIT_SetMediaAck(C1);
WAIT_Progress(C1);
}
}
Как видите всё на чистом C#, между тем DSL есть и сильно помогает. А своего парсера-транслятора-компилятора из DSL в чего-то там писать не пришлось.  :) :) :)

DIzer

  • Гость
Re: Про парсер и лексер.
« Ответ #91 : Сентябрь 14, 2012, 04:52:19 pm »
Ну не могут люди с низкой квалификацией сделать что-то большое.
Физически не могут.
Обязательно нужен надсмотрщик, который ходит и бьёт по рукам.
....
Абсолютно неадекватная аналогия. Я даже не могу понять, как она могла в голову прийти.
ммм. есть некоторая вероятность , что мы друг друга не понимаем.. я говорю про то, что есть различные уровни  "восприятия" сложности.. скажем так специалист по теоретической механике или еще лучше , по термодинамике может решать вполне СЛОЖНЫЕ задачи , без понимания внутренней структуры обьектов с которыми он работает (скажем воспользовавшись эмпирическими  законами, математикам ,так вообще хватает  аксиоматики)- и глупостью было бы предположить что некоторый "фундаментальщик"  разберется с пол -тычка с их заморочками...  более того, ИМХО человеку  который утверждает обратное стоило бы провериться в психдиспансере  ;).

DIzer

  • Гость
Re: Про парсер и лексер.
« Ответ #92 : Сентябрь 14, 2012, 04:56:20 pm »
Коль разговор скатывается к DSL (я правда так и не понял при чём тут контекстно зависимые лексемы), расскажу как я (этим летом) сделал некий аналог DSL без
....
И что,  если завернуть это дело , нормальным образом в библиотеку код так уж сильно распухнет?

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Re: Про парсер и лексер.
« Ответ #93 : Сентябрь 14, 2012, 05:45:49 pm »
Коль разговор скатывается к DSL (я правда так и не понял при чём тут контекстно зависимые лексемы), расскажу как я (этим летом) сделал некий аналог DSL без написания каких-либо парсеров вовсе.
...
 Как видите всё на чистом C#, между тем DSL есть и сильно помогает. А своего парсера-транслятора-компилятора из DSL в чего-то там писать не пришлось.  :) :) :)
Так это не DSL, а просто набор процедур...
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Про парсер и лексер.
« Ответ #94 : Сентябрь 14, 2012, 05:47:59 pm »
Коль разговор скатывается к DSL (я правда так и не понял при чём тут контекстно зависимые лексемы), расскажу как я (этим летом) сделал некий аналог DSL без написания каких-либо парсеров вовсе.
...
 Как видите всё на чистом C#, между тем DSL есть и сильно помогает. А своего парсера-транслятора-компилятора из DSL в чего-то там писать не пришлось.  :) :) :)
Так это не DSL, а просто набор процедур...
Это eDSL :-) По сути такой же какой в Haskell'e бейсик ;-)
Y = λf.(λx.f (x x)) (λx.f (x x))

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Про парсер и лексер.
« Ответ #95 : Сентябрь 14, 2012, 05:50:16 pm »
И что,  если завернуть это дело , нормальным образом в библиотеку код так уж сильно распухнет?
Я не понял вопроса.

Я сравнивал количество DSL-ассемлерных инструкций с количеством C# кода, который требовался для каждого теста до тех пор пока я не изобрёл для этой задачи DSL-ассемблер.

Сама виртуальная машина обрабатывающая DSL-ассемблерные инструкции сейчас занимает 2651 строчек C#.

Если нужно написать 100 тестов, то выбор очевиден: написание "в лоб" отнимет по 700 строчек C# на тест; написание на DSL-ассемблере -- примерно по 5-10 DSL-ассемблерных инструкций на тест.

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Про парсер и лексер.
« Ответ #96 : Сентябрь 14, 2012, 05:55:38 pm »
Так это не DSL, а просто набор процедур...
Именно! Сам DSL мне оказался нафиг не нужным, хватило его ассемблера. Набором процедур на C# эмичу DSL-ассеблерный код.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Про парсер и лексер.
« Ответ #97 : Сентябрь 14, 2012, 06:51:44 pm »
Кстати, для того, чтобы в языке общего назначения (например C#, C++) можно было полноценно делать eDSL нужна возможность не только добавлять возможности (в виде тех же процедур как у Сергея в примере), но и возможность запрещать/удалять что-то, грубо говоря, чтобы в блоке кода на этом eDSL гарантированно не было тех или иных вещей которые есть в родительском языке.
Y = λf.(λx.f (x x)) (λx.f (x x))

Geniepro

  • Hero Member
  • *****
  • Сообщений: 1955
  • Знайте- истина в том, что повторено трижды подряд!
    • Просмотр профиля
Re: Про парсер и лексер.
« Ответ #98 : Сентябрь 14, 2012, 07:47:41 pm »
Так это не DSL, а просто набор процедур...
Именно! Сам DSL мне оказался нафиг не нужным, хватило его ассемблера. Набором процедур на C# эмичу DSL-ассеблерный код.
Да, но тут есть серьёзная проблема -- нет защиты от ошибок в порядке вызова этих процедур, от ошибок в логике получающейся программы.

DSL же подразумевает более защищённый от ошибок язык, более высокоуровневый...
to iterate is human, to recurse, divine

Салат «рекурсия»: помидоры, огурцы, салат…

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Про парсер и лексер.
« Ответ #99 : Сентябрь 15, 2012, 10:03:29 am »
Да, но тут есть серьёзная проблема -- нет защиты от ошибок в порядке вызова этих процедур, от ошибок в логике получающейся программы.

DSL же подразумевает более защищённый от ошибок язык, более высокоуровневый...
Вызов процедур - эмичение инструкций. Например, вызов процедуры PASSED:
public void PASSED ()
{
this.Emit(new Halt(true, null));
}

Процедура  Emit:
public void Emit (Instruction inst)
{
if (this.program == null)
{
this.program = new Procedure();
}
this.program.Emit(inst);
}

Instruction - это:
public abstract class Instruction
{
public abstract bool Execute (Service x);
}

Procedure - это тоже инструкция:
public sealed class Procedure: Instruction
{
public readonly System.Collections.Generic.List<Instruction> instructions = new System.Collections.Generic.List<Instruction>();

public void Emit (Instruction inst)
{
this.instructions.Add(inst);
}

public override bool Execute (Service service)
{
return false;
}
}

Разный порядок ассемблерных инструкций - разные программы. Откуда DSL-компилятору знать какую именно программу мне нужно сейчас, чтобы подсказать мне, что я располагаю ассемблерные команды в неправильном порядке? Тут почти любой порядок будет давать в каком-то смысле правильную программу.

chucheloid

  • Newbie
  • *
  • Сообщений: 35
    • Просмотр профиля
Re: Про парсер и лексер.
« Ответ #100 : Сентябрь 15, 2012, 02:09:35 pm »
Коль разговор скатывается к DSL (я правда так и не понял при чём тут контекстно зависимые лексемы), расскажу как я (этим летом) сделал некий аналог DSL без написания каких-либо парсеров вовсе.
Это тебе просто с задачей повезло.
А могло и не повезти.
http://user1663.netfx45lab.discountasp.net/
Что бы делал?

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Про парсер и лексер.
« Ответ #101 : Сентябрь 15, 2012, 06:27:35 pm »
Что бы делал?
Не знаю. Я с Гуём не очень. Там надо динамически генерить html код? Ну, первое что приходит в голову, наверное я натворил бы композицию объектов, которые умеют сериализовываться в html. Это неплохо бы сработало когда надо генерировать типовой-однообразный html.

chucheloid

  • Newbie
  • *
  • Сообщений: 35
    • Просмотр профиля
Re: Про парсер и лексер.
« Ответ #102 : Сентябрь 16, 2012, 02:34:08 pm »
Не знаю. Я с Гуём не очень. Там надо динамически генерить html код? Ну, первое что приходит в голову, наверное я натворил бы композицию объектов, которые умеют сериализовываться в html. Это неплохо бы сработало когда надо генерировать типовой-однообразный html.
Там все намного сложнее, чем кажется. Открой демку Introduction и посмотри на код. Потом поменяй то, что написано в полях ввода. Нажми на кнопку и подумай, может ли этот код так себя вести... ;D

Губанов Сергей Юрьевич

  • Hero Member
  • *****
  • Сообщений: 590
    • Просмотр профиля
    • Домашняя страница
Re: Про парсер и лексер.
« Ответ #103 : Сентябрь 17, 2012, 10:52:56 am »
Открой демку Introduction и посмотри на код.
Я там ещё у странички запросил исходный код и получил 2312 строчечный html вот с такими названиями функций: MVCTest_SamplesViewModel__N_closureOf__N_lambda__8064_8256.

Веб для меня -- тёмный лес...

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Про парсер и лексер.
« Ответ #104 : Сентябрь 17, 2012, 11:11:27 am »
Открой демку Introduction и посмотри на код.
Я там ещё у странички запросил исходный код и получил 2312 строчечный html вот с такими названиями функций: MVCTest_SamplesViewModel__N_closureOf__N_lambda__8064_8256.

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

    <script src="/Scripts/jquery-1.8.0.min.js" type="text/javascript"></script>
    <script src="/Scripts/knockout-2.1.0.debug.js" type="text/javascript"></script>
    <script src="/Scripts/jquery.signalR-0.5.3.min.js" type="text/javascript"></script>
    <script src="/Scripts/sammy.js" type="text/javascript"></script>
    <script src="/Scripts/prettify.js" type="text/javascript"></script>
    <script src="/Scripts/lang-n.js" type="text/javascript"></script>
    <script src="/Scripts/linq.js" type="text/javascript"></script>
    <script src="/signalr/hubs" type="text/javascript"></script>

хотя, возможно как минимум часть этих либ нужна лишь для подсветки синтаксиса исходника странички и так далее.
)
Y = λf.(λx.f (x x)) (λx.f (x x))