среда, 6 января 2010 г.

Delphi переделываем цикл For на While

С новым годом!
Так получилось, что руководству захотелось попробовать перейти во всём проекте с Integer на Int64. Причём перейти хотелось малой кровью, простой заменой. Вроде ничего сложного, но есть в этом деле не приятные моменты.
Я не говорю о функциях которые описаны в системных модулях, которым не нравится, что в них подставляют Int64, а не ожидаемый Integer.
Я говорю, о проблеме с циклом For, хороший цикл, и очень часто используется в нашем проекте. Но вот проблема, у цикла итератор может быть только Integer, и что делать? Как быть? Как победить эту напасть?

Вот цикл While прекрасно живёт с любым типом итераторов. Всего то и нужно что переделать все For на While. Ну задачка, как задачка, нужно перебрать пару сотен модулей и переделать циклы. Да к тому же и компилятор показывает места с ошибками.
Но зачем такие жертвы если можно воспользоваться прекрасным инструментом Refactoring. К сожалению похоже я не умею его готовить, и ума с циклом он мне не дал. Вот и пришлось написать маленький скрипт на Python который будет бегать по всем модулям программы и менять циклы.
Что должен уметь данный скрипт:

  1.   Получить список файлов в папке (директории) и поддиректориях.
  2.   Проверить каждую строку файла на совпадение с регулярным выражением.
  3.   Преобразовать совпавшую строку используя части из регулярного выражения.
  4.   Заменить старое содержание файла новым.


Давайте приступим к написанию скрипта: 

#! -*- coding: cp1251 -*-

import re
import os, sys, fnmatch

def do_new_string(before_line, now_line):
    '''
        before_line - строка которая проверялась последней.
        now_line    - строка которая проверяется сейчас.
        Если в now_line содержиться оператор begin то будет построен блок while 
        без указания этого оператора.
        Если в now_line нет begin значит цикл For применялся к одной строке и оператор
        необходимо добавить.  
        
        функция преобразует строку вида:
        For I := 0 to List.Count - 1 do
          J := J * I;
        в несколько строк вида
        I := 0-1;
        while I < List.Count - 1 - 1 do begin
          inc(I);
          J := J * I;
          
        функция возвращает новые строки и статус 1, если были изменения, и строку before_line и статус 0
        если изменений нет.
    '''

    newline = before_line
    last_chang = 0
    
    # регулярное выражение можно прочитать так:
    # найти в строке обязательное слово for, за которым следует любое слово  
    # после которого присутствует := затем должно быть любое слово, в котором 
    # могут встречаться арифметические знаки и пропуски. После может быть 
    # не обязательное совпадение down и обязательное совпадение to, за которым 
    # следует слово в котором могут быть арифметические знаки и пропуски, 
    # затем должно встретиться слово do.
    # Удовлетворение этим условиям приведёт к совпадению выражения
    reg = re.compile(r'(\s*)(for)\s*((\w)\s*:=\s*[\w().+\-*\\\s]*)(down)?to\s*([\w()\[\].+\-*\\\s]*)(do)(.*)', re.I)
    find = reg.findall(before_line)
    if now_line.lower().find('begin') == -1:
        if len(find) > 0:
            # Если текущая строка не содержит begin и в предыдущей нашлось совпадение для
            # выражения то в зависимости от присутствия оператора downto или to строим 
            # блок While
            if reg.findall(before_line)[0][4] != "":
                newline = reg.sub(r"\1\3+1;\n\1while \4 > \6 \7 begin\n\1  dec(\4);", before_line)
            else:
                newline = reg.sub(r"\1\3-1;\n\1while \4 < \6 - 1 \7 begin\n\1  inc(\4);", before_line)
            last_chang = 1
    else:
        if len(find) > 0:
            if reg.findall(before_line)[0][4] != "":
                newline = reg.sub(r"\1\3+1;\n\1while \4 > \6 - 1 \6", before_line)
                newline = newline + now_line
                newline = reg.sub(newline + r"\1  dec(\4)", before_line)
            else:   
                newline = reg.sub(r"\1\3-1;\n\1while \4 < \6 - 1 \6", before_line)
                newline = newline + now_line
                newline = reg.sub(newline + r"\1  inc(\4)", before_line)
            last_chang = 1
    return newline, last_chang

def do_file_work(_file):
    '''
        Функция работает с указанным файлом по строкам
    '''
    f = open(_file) # Opens the processed file
    ff = open('temp.txt', 'w') # Creates a temporary file
    old_line = ""
    print(_file)
    for ss in f:
        if old_line.strip().upper() != 'BEGIN':
            res = do_new_string(old_line, ss)
            ff.writelines(res[0])
        else:
            if res[1] == 0:
                ff.writelines('begin\n')   
        old_line = ss
  
    ff.writelines(old_line)
    f.close()
    ff.close()
    os.remove(_file)
    os.rename('temp.txt', _file)


def dodir(dirtodo):
    '''
       Функция перебирает все файлы в указанной директории, а так же файлы во вложенных папках
    '''
    ld = os.listdir(dirtodo)
    for f in ld:
        path = dirtodo +"\\"+ f
        if os.path.isdir(path):
            dodir(path)
        else:
            if fnmatch.fnmatch (path, '*.pas' ) | fnmatch.fnmatch (path, '*.tayp' ):
                    do_file_work(path)

# Начинаем работу. В параметр скрипта необходимо передать путь до рабочий директории
if os.path.exists(sys.argv[1]):
    dodir(sys.argv[1])

       
print('Done')   
   
if __name__ == '__main__':
    pass

Вот собственно рабочий скрипт.
Правда он теряет тело цикла, если цикл написан в одну строчку. И не доставляет end; в конце тела нового цикла ели его не было.
Тем не менее, мне удалось поменять все циклы for на while в течение получаса, после запуска скрипта.
К сожалению или счастью, но идея смены Integer на Int64 провалилась.

5 комментариев:

  1. А твоё руководство назвало объективные причины перехода на int64? По моему это чистый бред.

    ОтветитьУдалить
  2. Идея заключалась в том, что необходимо увеличить размерность Домена в БД с Integer на Int64, а соответственно и в клиенте. В большинстве мест в клиенте используется специальный класс TID, который унаследован от Integer. Поидее, замены его родителя на Int64 достаточно.
    Но есть подозрение, что не все разработчики добросовестно использовали этот, TID, во всех местах где это необходимо.
    Поэтому для избежания случаев с переполнением было сказано попробовать заменить все Integer без разбору. Ну а оттуда и полезли все бяки. Одна из них описана выше.

    ОтветитьУдалить
  3. " увеличить размерность Домена в БД с Integer на Int64" - это как?

    " специальный класс TID, который унаследован от Integer." - путаешь с пользовательским типом, который по сути и есть Integer. Классов там у вас нет.

    "заменить все Integer без разбору." - бред чистой воды. Смысл менять тип итератора в цикле от 1 до 31 (допустим по дням месяца) с integer на int64...

    А вообще надо по месту разбираться и скорее всего выщимлять все места и менять типы руками там где надо. А потом долго тестировать основные места.

    ЗЫЖ Я не думаю, что нормальный разработчик делал бы итератор по ID... скорее итераторы бегают по элементам некоторого списка, в котором лежат эти самые ID с доп. информацией

    ОтветитьУдалить
  4. ну был домен DID он был Integer теперь его сделают Int64.
    Да ты прав бред чистой воды. Но всё это имеет место быть.

    ОтветитьУдалить
  5. хм, странные конечно желания заменить вообще все типы Integer на Int64 по проекту :), но хозяин - барин.

    ОтветитьУдалить