четверг, 3 сентября 2015 г.

#1140. Кодогенерация. Развитие

Предыдущая серия была тут - Кодогенерация. Вынесем конкретную модель и конкретные шаблоны во внешние словари.

Я ещё немного поработал и вот что пока получается:

// Generation.ms.dict

USES
 Documentation.ms.dict
 EngineTypes.ms.dict
 axiom_push.ms.dict
 macro.ms.dict
 core.ms.dict
 NoStrangeSymbols.ms.dict
 params.ms.dict
 NoCapsLock.ms.dict
 arrays.ms.dict
 WordsRTTI.ms.dict
 ElementsRTTI.ms.dict
 string.ms.dict
;

INTEGER VAR g_Indent
%REMARK 'Текущий отступ'
( g_Indent := 0 )

BOOLEAN elem_func IsElementNeed.Indent
 %SUMMARY 'Определяет тот факт, что элементу нужен отступ' ;
 Result := true
; // IsElementNeed.Indent

elem_proc EnterElement
 %SUMMARY 'Начинает вывод элемента' ;
 Self .IsElementNeed.Indent ? INC g_Indent
; // EnterElement

elem_proc LeaveElement
 %SUMMARY 'Заканчивает вывод элемента' ;
 Self .IsElementNeed.Indent ? DEC g_Indent
; // LeaveElement

FILE VAR g_OutFile
( g_OutFile := nil )

STRING INTEGER ARRAY TYPE OUTABLE

CONST cIndentChar ' '

PROCEDURE OutToFile
 OUTABLE IN aValue 
 %SUMMARY 
 '
 Выводит значение в текущий файл вывода.
 С переводом каретки.
 '
 ; // %SUMMARY 

 STRING VAR l_String
 if ( aValue IsArray ) then
  ( aValue strings:Cat =: l_String )
 else
  ( aValue ToPrintable =: l_String )
 [ g_Indent cIndentChar char:Dupe l_String ] strings:Cat g_OutFile File:WriteLn
 %REMARK '- выводим элементы модели в файл, а не в стандартный вывод.'
; //OutToFile

PROCEDURE ?OutToFile
 STRING IN aValue
 %SUMMARY 
 '
 Выводит значение в текущий файл, если оно не пустое.
 С переводом каретки.
 '
 ; // %SUMMARY 
 if ( aValue =/= '' ) then
  ( aValue OutToFile )
; // ?OutToFile

FUNCTOR TYPE GENERATOR
%REMARK 'Генератор содержимого элемента'

GENERATOR VAR g_CurrentGenerator
%REMARK 'Текущий генератор'
( g_CurrentGenerator := nil )

STRING VAR g_CurrentGeneratorName
%REMARK 'Имя текущего генератора'
( g_CurrentGeneratorName := '' )

CONST cPathSep '\'

STRING FUNCTION OutDir
 sysutils:GetCurrentDir =: Result
 [ Result
  script:FileName 
  %REMARK 'Путь к текущему скрипту'
  sysutils:ExtractFileName
  %REMARK 'Вырезаем из пути только имя файла' 
  '' sysutils:ChangeFileExt
  %REMARK 'Убираем .script' 
  '' sysutils:ChangeFileExt 
  %REMARK 'Убираем .ms' 
 ] cPathSep strings:CatSep =: Result
; // OutDir

STRING FUNCTION OutFileName
 STRING right aGeneratorName
 %SUMMARY 'Имя файла для вывода' ;
 STRING VAR l_OutPath
 %REMARK 'Путь для вывода'
 l_OutPath := OutDir
 l_OutPath sysutils:ForceDirectories ?ASSURE [ 'Не удалось создать директорию ' l_OutPath ]
 %REMARK 'Создаём директорию рекурсивно, если её ещё не было'
 [ l_OutPath aGeneratorName ] cPathSep strings:CatSep =: Result
; // OutFileName

BOOLEAN elem_func IsStereotypeOf
 LINK RIGHT IN aStereo
 %SUMMARY 'Определяет, что элемент является указанным стереотипом или его потомком' ;
 Result := ( Self .Stereotype .Name = ( aStereo .Name ) ) 
; // IsStereotypeOf

USES
 CommonLang.ms.model
;

// - это - "времянка", чтобы увидеть символ <<Const>>

BOOLEAN elem_func IsElementNeed.OwnFile
 %SUMMARY 'Определяет тот факт, что элементу нужен собственный файл вывода' ;
 Result := true
 if ( Self .IsStereotypeOf <<Const>> ) then
 begin
  Result := false
 end
; // IsElementNeed.OwnFile

ModelElement VAR g_CurrentElementModel
 %REMARK 'Текущий генерируемый элемент'
( g_CurrentElementModel := nil )

STRING VAR g_OutFileName
( g_OutFileName := '' )

elem_proc CallCurrentGen
 FILE VAR l_PrevOutFile
 l_PrevOutFile := g_OutFile
 INTEGER VAR l_PrevIndent
 l_PrevIndent := g_Indent
 TRY
  TRY
   STRING VAR l_PrevOutFileName
   l_PrevOutFileName := g_OutFileName
   TRY
    if ( Self .IsElementNeed.OwnFile ) then
    begin
     g_Indent := 0 // - тут вообще ещё надо проверять - переоткрываем мы файл или нет
     STRING VAR l_FileName
     g_OutFileName := ( g_OutFileName ?(+) '_' (+) ( Self .Name ) )
     l_FileName := ( OutFileName g_OutFileName (+) '.' (+) g_CurrentGeneratorName )
     g_OutFile := ( l_FileName File:OpenWrite )
    end // Self .IsElementNeed.OwnFile
    TRY
     g_CurrentGenerator IsNil ! ?ASSURE 'Текущй генератор пустой'
     ModelElement VAR l_CurrentElementModel
     l_CurrentElementModel := g_CurrentElementModel
     TRY
      g_CurrentElementModel := Self
      Self ( g_CurrentGenerator DO )
      %REMARK 'Вызываем на элементе генератор g_CurrentGenerator'
     FINALLY
      g_CurrentElementModel := l_CurrentElementModel
     END
    FINALLY
     g_OutFile := nil
    END // TRY..FINALLY
   FINALLY
    g_OutFileName := l_PrevOutFileName
   END
  FINALLY
   g_OutFile := l_PrevOutFile
   l_PrevOutFile := nil
  END
 FINALLY
  g_Indent := l_PrevIndent
 END
; // CallCurrentGen

elem_proc Child.CallCurrentGen
 %SUMMARY 'Вызывает на ДОЧЕРНЕМ элементе генератор g_CurrentGenerator с учётом отступов' ;
 Self .EnterElement 
 TRY
  Self .CallCurrentGen
  //Self g_CurrentGenerator DO
  %REMARK 'Вызываем генератор g_CurrentGenerator'
 FINALLY
  Self .LeaveElement 
 END // TRY..FINALLY
; // Child.CallCurrentGen

elem_proc CallChildrenCurrentGen
 %SUMMARY 'Вызывает текущий генератор для всех детей элемента модели' ;
 Self .Children .for> .Child.CallCurrentGen
; // CallChildrenCurrentGen

WordAlias .generate.children .CallChildrenCurrentGen

CONST GEN_PROPERTY_PREFIX 'gp'
%REMARK 'Префикс имени свойства генератора'

MACRO %GEN_PROPERTY
 Literal IN aName
 %SUMMARY 'Свойство генератора' ;
 this.method.addr Ctx:SetWordProducerForCompiledClass
 axiom:PushSymbol CONST
 GEN_PROPERTY_PREFIX (+) ( aName .Name ) Ctx:Parser:PushSymbol
; // %GEN_PROPERTY

USES
 RefDeepest.ms.dict
 FieldByNameDeepest.ms.dict
;

MACRO %GP
 Literal IN aName
 %SUMMARY 'Метод получения свойства генератора' ;
 axiom:PushSymbol FieldByNameDeepest
 GEN_PROPERTY_PREFIX (+) ( aName .Name ) Ctx:Parser:PushSymbol
; // %GP

elem_proc CallGen
 GENERATOR RIGHT IN aGen

 %SUMMARY 
 '
 Вызывает на элементе генератор aGen.
 С открытием "правильных файлов".
 ' 
 ; // %SUMMARY 

 aGen Dereference =: g_CurrentGenerator
 TRY
  aGen %GP Name =: g_CurrentGeneratorName
  Self .CallCurrentGen
 FINALLY
  g_CurrentGenerator := nil
 END
; // CallGen

ARRAY VAR g_AllRoots
 %REMARK 'Все корневые элементы'
( g_AllRoots := nil )

ARRAY VAR g_AllGenerators
 %REMARK 'Все генераторы'
( g_AllGenerators := nil )

PROCEDURE CallGens
 ARRAY IN anElements
 ARRAY IN aGenerators
 %SUMMARY 'Вызывает все определённые генераторы на элементах массива anElements' ;
 OutDir PureDir
 %REMARK 'Очищаем директорию от "старых" файлов'
 g_AllRoots := anElements
 TRY 
  g_AllGenerators := aGenerators
  TRY
   for anElements (
    ModelElement IN anElement
    for aGenerators ( 
     GENERATOR IN aGen 
     anElement .CallGen aGen
     %REMARK 'Вызываем на элементе anElement генератор aGen' 
    ) // for aGenerators
   ) // for anElements
  FINALLY
   g_AllGenerators := nil
  END
 FINALLY
  g_AllRoots := nil
 END
; // CallGens

PROCEDURE CallGensList
 Sequence LVALUE anElements
 Sequence LVALUE aGenerators
 ( anElements CodeIterator )
  ( aGenerators CodeIterator )
   CallGens
; // CallGensList

WordAlias generate CallGensList

Стоит обратить внимание на IsElementNeed.OwnFile и IsStereotypeOf.

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

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