понедельник, 15 апреля 2013 г.

DSL-ный хардкор


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

Приведу лишь практические изыскания на тему.

Ниже приведён файл, который разбирает один входной поток и строит - другой. (Полный текст тут - https://www.box.com/s/p7dzynlw7d9klk2gwez1)

Поскольку он "типа" самодокументируемый, то пока дальнейшие коммментарии "от себя" - опускаю.
----------------------------

USES
 'W:\shared\models\NewSchool\Templates\MDATuning.tpl.script'


: "Ничего не делаем"
; // "Ничего не делаем"

: log
 .
 //DROP
; // log

WORDWORKER "начинается с" W-STRING IN aStr
 WordToWork DO aStr WString:Starts
; // "начинается с"

// InitedVarProducer VAR-I

: "Обработать шаблоны MDA"

 ARRAY "Обработанные файлы"

 FORWARD "Обработать файл"

 : "Обработать файл" STRING IN "Имя входного файла"

  "Обработанные файлы" "Имя входного файла" array:HasText ? (
   [[ 'duplicated: ' "Имя входного файла" ' skipped' ]] strings:Cat log
   EXIT
  ) // "Обработанные файлы" "Имя входного файла" array:HasString ?
  
  "Имя входного файла" >>>[] "Обработанные файлы"

  STRING VAR "Путь к входному файлу"
  "Имя входного файла" sysutils:ExtractFilePath =: "Путь к входному файлу"
  "Путь к входному файлу" log

  VAR "Имя выходного файла" 
  "Имя входного файла" '.script' Cat =: "Имя выходного файла"
  
  CONST "Пробел" ' '
  CONST "Пустая строка" ''
  CONST "Кавычка" ''''
  CONST "Открывающаяся скобка" '('
  CONST "Закрывающаяся скобка" ')'
  CONST "Запятая" ','
  CONST "Знак процента" '%'
  CONST "Спецсимволы" '\%[]{}<>#()'
  CONST "Цифры" '1234567890'
  CONST "Разделитель частей стереотипа" '::'
  
  STRING VAR "Имя диска"
  "Имя выходного файла" sysutils:ExtractFileDrive =: "Имя диска"
  
  '\' string:SplitTo! "Имя выходного файла"
  "Имя диска" ?== 'Имя файла не содержит указание диска' ASSERTS
  
  [[ "Имя диска" '\NewSchool\' "Имя выходного файла" ]] strings:Cat =: "Имя выходного файла"
  
  "Имя выходного файла" log
  
  STRING VAR "Путь к выходному файлу"
  "Имя выходного файла" sysutils:ExtractFilePath =: "Путь к выходному файлу"
  "Путь к выходному файлу" log
  
  "Путь к выходному файлу" sysutils:ForceDirectories 'Не удалось создать директории' ASSERTS
  
  //script:FileName sysutils:ExtractFileName "Пустая строка"  "Пробел" Cat "Имя выходного файла" sysutils:ExtractFileName Cat =: "Имя выходного файла"
  
  FILE VAR "Входной файл"
  "Имя входного файла" file:OpenRead =: "Входной файл"
  TRY
   FILE VAR "Выходной файл"
   "Имя выходного файла" file:OpenWrite =: "Выходной файл"
   TRY
   
    W-STRING VAR "Текущая строка входного файла"
    
    : "Строка пустая"
     "Текущая строка входного файла" WString:IsNil
    ; // "Строка пустая"
    
    WORDWORKER "Строка начинается с"
     VAR l_Begin
     WordToWork DO =: l_Begin
     "Текущая строка входного файла" "начинается с" l_Begin
    ; // "Строка начинается с"
   
    WORDWORKER "Строка равна"
     VAR l_EQ
     WordToWork DO =: l_EQ
     "Текущая строка входного файла" l_EQ ?==
    ; // "Строка начинается с"
    
     BOOLEAN VAR "Накапливаемая строка пустая"
     BOOLEAN VAR "Была кавычка"
      
     : "Вывести строку"
      "Выходной файл" file:WriteStr
     ; // "Вывести строку"
    
     : "Вывести кавычку"
      "Кавычка" "Вывести строку"
     ; // "Вывести кавычку"
     
     : "Закрыть кавычку, если была"
      "Была кавычка" ? (
       "Вывести кавычку"
       false =: "Была кавычка"
      ) // "Была кавычка" ?
     ; // "Закрыть кавычку, если была"
      
     : "Вывести пробел"
      "Пробел" "Вывести строку"
     ; // "Вывести пробел"
     
     : "Сбросить накопленную строку без перевода"
      "Накапливаемая строка пустая" ! ? (
      
       "Закрыть кавычку, если была"
        
       "Вывести пробел" 
       true =: "Накапливаемая строка пустая"
       false =: "Была кавычка"
      ) // "Накапливаемая строка" string:Len !=0 ?
     ; // "Сбросить накопленную строку без перевода"
     
     BOOLEAN VAR "Был отступ"
     
     : "Перевести строку"
      "Закрыть кавычку, если была"
      "Пустая строка" "Выходной файл" file:WriteLn
      false =: "Был отступ"
      "Была кавычка" ! 'Похоже не закрыли кавычку' ASSERTS 
      //false =: "Была кавычка"
     ; // "Перевести строку"
     
     : "Сбросить накопленную строку"
      "Накапливаемая строка пустая" ! ? (
      
       "Закрыть кавычку, если была"
        
       "Перевести строку"
       true =: "Накапливаемая строка пустая"
      ) // "Накапливаемая строка" string:Len !=0 ?
     ; // "Сбросить накопленную строку"
     
     : "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
      "Сбросить накопленную строку"
     ; // "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
      
     : "Вывести строку как есть"
      "Текущая строка входного файла" "Выходной файл" file:WriteWStrLn
     ; // "Вывести строку как есть"
     
     : "Добавить к строке / и вывести"
      "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
      '/' "Вывести строку"
      "Вывести строку как есть"
     ; // "Добавить к строке / и вывести"
     
     : "Вывести строку как комментарий. Чтобы в конечном файле было с чем сравнивать"
      "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
      "Был отступ"  ? "Перевести строку"
      '//' "Вывести строку"
      "Вывести строку как есть"
      false =: "Был отступ"
     ; // "Вывести строку как комментарий. Чтобы в конечном файле было с чем сравнивать"
     
     BOOLEAN VAR "Был открыт стереотип" 
      false =: "Был открыт стереотип"
     STRING VAR "Имя стереотипа"
      "Пустая строка" =: "Имя стереотипа" 
     STRING VAR "Имя класса стереотипа"
      "Пустая строка" =: "Имя класса стереотипа" 
     STRING VAR "Имя под-стереотипа"
      "Пустая строка" =: "Имя под-стереотипа" 
     STRING VAR "Имя класса под-стереотипа"
      "Пустая строка" =: "Имя класса под-стереотипа" 
     STRING VAR "Имя под-под-стереотипа"
      "Пустая строка" =: "Имя под-под-стереотипа" 
     STRING VAR "Имя класса под-под-стереотипа"
      "Пустая строка" =: "Имя класса под-под-стереотипа"
     
     BOOLEAN VAR "Была открыта функция"
      false =: "Была открыта функция" 
     STRING VAR "Имя функции"
      "Пустая строка" =: "Имя функции"
     BOOLEAN VAR "Был открыт трансформатор"
      false =: "Был открыт трансформатор" 
     STRING VAR "Имя трансформатора"
      "Пустая строка" =: "Имя трансформатора"
     
     BOOLEAN VAR "Был открыт генератор"
      false =: "Был открыт генератор"
     STRING VAR "Имя генератора"
      "Пустая строка" =: "Имя генератора"
     
     CONST Разделители ' '
     CONST "Двойная кавычка" '"'
     CONST "Параметр Self" ' OBJECT IN %S'
     CONST "Скобка закрытия функции" '; // '
     CONST "Открываем строку стереотипа" '<<'
     CONST "Закрываем строку стереотипа" '>>'
     CONST "Табуляция" #9
     
     : "Вывести строку с переводом строки"
      "Вывести строку"
      "Перевести строку"
     ; // "Вывести строку с переводом строки"
     
     : "Вывести исходную строку"
      "Текущая строка входного файла" "Выходной файл" file:WriteWStr
     ; // "Вывести исходную строку"
     
     : "Записать имя стереотипа"
      "Имя под-стереотипа" string:Len !=0 IF
       ':: ' "Вывести строку"
       "Имя стереотипа" "Вывести строку"
       "Пробел" "Вывести строку"
       "Имя под-стереотипа" "Вывести строку"
       
       "Имя под-под-стереотипа" string:Len !=0 IF
        "Пробел" "Вывести строку"
        "Имя под-под-стереотипа" "Вывести строку"
       ENDIF // "Имя под-под-стереотипа" string:Len !=0 IF
       
       ' ;' "Вывести строку"
       
      ELSE
       "Имя стереотипа" "Вывести строку"
      ENDIF //  "Имя под-стереотипа" string:Len !=0
      "Перевести строку"
     ; // "Записать имя стереотипа"
     
     : "Записать имя функции"
      "Имя функции" "Вывести строку"
     ; // "Записать имя функции"
     
     : "Записать имя трансформатора"
      "Имя трансформатора" "Вывести строку"
     ; // "Записать имя трансформатора"
     
     : "Записать имя генератора"
      "Имя генератора" "Вывести строку"
     ; // "Записать имя генератора"
     
     FORWARD "Закрыть вложенные опеределения"
     
     : "Закрыть стереотип"
      "Был открыт стереотип" ?
       ( 
        "Закрыть вложенные опеределения"
        'end. // ' "Вывести строку" 
        "Записать имя стереотипа"
        "Перевести строку"
       ) // "Был открыт стереотип" ?
      false =: "Был открыт стереотип"
     ; // "Закрыть стереотип"
     
     INTEGER VAR "Отступ"
     INTEGER VAR "Количество открытых IF"
     INTEGER VAR "Количество открытых циклов"
     INTEGER VAR "Количество открытых скобок параметров функции"
      
     : "Закрыть все скобки"
      0 =: "Отступ"
      0 =: "Количество открытых IF"
      0 =: "Количество открытых циклов"
      0 =: "Количество открытых скобок параметров функции"
      "Закрыть вложенные опеределения"
      "Закрыть стереотип"
     ; // "Закрыть все скобки"
     
     : "Закавычить имя стереотипа"
      Разделители "Имя стереотипа" string:HasAnyOf ? (
       [[ "Двойная кавычка" "Имя стереотипа" "Двойная кавычка" ]]
        strings:Cat =: "Имя стереотипа"
      )
     
      "Имя класса стереотипа" 'MDAGenerator' ?!= ? (
       [[ "Открываем строку стереотипа" "Имя стереотипа" "Закрываем строку стереотипа" ]] 
        strings:Cat =: "Имя стереотипа"
      )
     ; // "Закавычить имя стереотипа"
     
     : "Закавычить имя под-стереотипа"
      "Имя под-стереотипа" string:Len !=0 ? (
       Разделители "Имя под-стереотипа" string:HasAnyOf ? (
        [[ "Двойная кавычка" "Имя под-стереотипа" "Двойная кавычка" ]]
         strings:Cat =: "Имя под-стереотипа"
       ) // Разделители "Имя под-стереотипа" string:HasAnyOf ?
      
       // "Имя класса стереотипа" 'MDAGenerator' ?!= 
       true ? (
        [[ "Открываем строку стереотипа" "Имя под-стереотипа" "Закрываем строку стереотипа" ]] 
         strings:Cat =: "Имя под-стереотипа"
       ) // true ?
      ) // "Имя под-стереотипа" string:Len !=0 ?
     ; // "Закавычить имя под-стереотипа"
     
     : "Открыть стереотип"
      "Был открыт стереотип" ! ? (
        'implementation @ ' "Вывести строку"
        
        "Записать имя стереотипа"
        true =: "Был открыт стереотип"
      ) // "Был открыт стереотип" !
     ; // "Открыть стереотип"
     
     : "Разобрать заголовок стереотипа"
     
      "Закрыть стереотип"
      
      2 WString:+! "Текущая строка входного файла"
      "Текущая строка входного файла" WString:ToString =: "Имя стереотипа"
      
      "Имя стереотипа" "Разделитель частей стереотипа" string:Split
      
      =: "Имя класса стереотипа"
      =: "Имя стереотипа"
      
      "Имя класса стереотипа" "Разделитель частей стереотипа" string:Split
      =: "Имя под-стереотипа"
      =: "Имя класса стереотипа"
      
      "Имя под-стереотипа" "Разделитель частей стереотипа" string:Split
      =: "Имя класса под-стереотипа"
      =: "Имя под-стереотипа"
      
      "Имя класса под-стереотипа" "Разделитель частей стереотипа" string:Split
      =: "Имя под-под-стереотипа"
      =: "Имя класса под-стереотипа"
      
      "Закавычить имя стереотипа"
      "Закавычить имя под-стереотипа"
      
      "Открыть стереотип"
      
     ; // "Разобрать заголовок стереотипа"
     
     : "Закрыть функцию"
      "Была открыта функция" ? ( "Скобка закрытия функции" "Вывести строку" "Записать имя функции" 
      "Перевести строку"
      "Перевести строку"
      )
      false =: "Была открыта функция"
     ; // "Закрыть функцию"
     
     : "Закрыть трансформатор"
      "Был открыт трансформатор" ? ( "Скобка закрытия функции" "Вывести строку" "Записать имя трансформатора" 
      "Перевести строку"
      "Перевести строку"
      )
      false =: "Был открыт трансформатор"
     ; // "Закрыть трансформатор"
     
     : "Закрыть генератор"
      "Был открыт генератор" ? ( "Скобка закрытия функции" "Вывести строку" "Записать имя генератора" 
      "Перевести строку"
      "Перевести строку"
      )
      false =: "Был открыт генератор"
     ; // "Закрыть генератор"
     
//     : "Было открыто хоть одно вложенное определение"
//      "Была открыта функция" %|| 
//      "Был открыт трансформатор" %|| 
//      "Был открыт генератор"
//     ; // "Было открыто хоть одно вложенное определение"
     
     : "Закрыть вложенные опеределения"
      "Закрыть генератор"
      "Закрыть функцию"
      "Закрыть трансформатор"
      false =: "Был отступ"
     ; // "Закрыть вложенные опеределения"
     
     : "Записать параметры функции" BOOLEAN IN aGlobal
      "Параметр Self" "Вывести строку"
     ; // "Записать параметры функции"
     
     : "Записать параметры трансформатора" BOOLEAN IN aGlobal
      "Параметр Self" "Вывести строку"
     ; // "Записать параметры трансформатора"
     
     : "Разобрать заголовок функции" BOOLEAN IN aGlobal
      "Закрыть вложенные опеределения"
      aGlobal IF
       "Закрыть стереотип"
      ELSE
       "Открыть стереотип"
      ENDIF 
      
      aGlobal IF
       2 WString:+! "Текущая строка входного файла"
       // - отрезаем f
      ELSE 
       3 WString:+! "Текущая строка входного файла"
       // - отрезаем %f
      ENDIF
       
      "Строка начинается с" '_' ? WString:++! "Текущая строка входного файла"
      // - отрезаем поддчёркивание
      "Текущая строка входного файла" WString:ToString =: "Имя функции"
      // - получаем имя текущей функции
      
      ': ' "Вывести строку"
      "Записать имя функции"
      
      aGlobal "Записать параметры функции"
      
      "Перевести строку"
      
      true =: "Была открыта функция"
     ; // "Разобрать заголовок функции"
     
     : "Разобрать заголовок трансформатора" IN aGlobal
      "Закрыть вложенные опеределения"
      aGlobal IF
       "Закрыть стереотип"
      ELSE
       "Открыть стереотип"
      ENDIF 
      
      aGlobal IF
       2 WString:+! "Текущая строка входного файла"
       // - отрезаем t
      ELSE 
       3 WString:+! "Текущая строка входного файла"
       // - отрезаем %t
      ENDIF
       
      "Строка начинается с" '_' ? WString:++! "Текущая строка входного файла"
      // - отрезаем поддчёркивание
      "Текущая строка входного файла" WString:ToString =: "Имя трансформатора"
      // - получаем имя текущего трансформатора
      
      '<<transformator>> ' "Вывести строку"
      "Записать имя трансформатора"
      
      aGlobal "Записать параметры трансформатора"
      
      "Перевести строку"
      
      true =: "Был открыт трансформатор"
     ; // "Разобрать заголовок трансформатора"
     
     : "Разобрать заголовок функции стереотипа"
      false "Разобрать заголовок функции"
     ; // "Разобрать заголовок функции стереотипа"
     
     : "Разобрать заголовок глобальной функции"
      true "Разобрать заголовок функции"
     ; // "Разобрать заголовок глобальной функции"
     
     : "Разобрать заголовок трансформатора стереотипа"
      false "Разобрать заголовок трансформатора"
     ; // "Разобрать заголовок трансформатора стереотипа"
     
     : "Разобрать заголовок глобального трансформатора"
      true "Разобрать заголовок трансформатора"
     ; // "Разобрать заголовок глобального трансформатора"
     
     : "Записать параметры генератора"
      "Параметр Self" "Вывести строку"
     ; // "Записать параметры генератора"
     
     : "Разобрать заголовок генератора"
      "Закрыть вложенные опеределения"
      
      2 WString:+! "Текущая строка входного файла"
      "Текущая строка входного файла" WString:ToString =: "Имя генератора"
      
      "Открыть стереотип"
      
      '<<generator>> ' "Вывести строку"
      "Записать имя генератора"
      
      "Записать параметры генератора"
      
      "Перевести строку"
      
      true =: "Был открыт генератор"
     ; // "Разобрать заголовок генератора"
     
     BOOLEAN FUNCTION "Обрабатываем незначащие строки" BOOLEAN IN "Надо переводить строку"
     
      : "Вывести комментарий, котрый был в исходном файле"
       "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
       "Надо переводить строку" IF
        "Перевести строку"
       ELSE
        "Был отступ"  ? "Перевести строку"
       ENDIF // "Надо переводить строку"
       "Вывести строку как есть"
      ; // "Вывести комментарий, котрый был в исходном файле"
     
      : "Сигнализировать о неуспехе"
       false =: Result
      ; // "Сигнализировать о неуспехе"
      
      true =: Result
      // - будем оптимистами
      RULES
       "Строка пустая"
        ( 
         "Закрыть кавычку, если была"
         "Надо переводить строку" ? "Перевести строку"
         "Вывести строку как есть"
        ) // "Строка пустая"
       "Строка начинается с" '//#UC END# *'
        (
         "Вывести комментарий, котрый был в исходном файле"
         "Закрыть вложенные опеределения"
          // - т.к. наверное функция, трансформатор или генератор - закончились
        ) 
       "Строка начинается с" '//'
        "Вывести комментарий, котрый был в исходном файле"
       "Строка начинается с" '/'
        (
         "Надо переводить строку" ? "Перевести строку"
         "Добавить к строке / и вывести"
        ) // "Строка начинается с" '/'
       DEFAULT
        "Сигнализировать о неуспехе"
      ; // RULES
     ; // "Обрабатываем незначащие строки"
     
     : "Обрабатываем незначащие строки без перевода строки перед ними"
      false "Обрабатываем незначащие строки"
     ; // "Обрабатываем незначащие строки без перевода строки перед ними"
     
   
Полный текст тут - https://www.box.com/s/p7dzynlw7d9klk2gwez1

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

Попробуйте подумать в этом направлении. Может быть вам понравится.

Похожая тема - http://18delphi.blogspot.com/2013/04/forth-vs-lisp.html

Комментариев нет:

Отправить комментарий