OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Воскресенье, 20 Октябрь, 2019 12:19

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 178 ]  На страницу Пред.  1 ... 3, 4, 5, 6, 7, 8, 9  След.
Автор Сообщение
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 07:17 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2316
Откуда: Россия, Томск
Я считаю, что из троицы конструкторы-деструкторы-исключения Оберону нужны прежде всего конструкторы. Компонентному Паскалю конструкторы не нужны в связи с наличием ABSTRACT'ных записей. Деструкторы в виде FINALIZE есть везде, а при желании вызывать их вручную это элементарно реализуется дополнительным методом.
Есть, правда, неопределённость со стековыми записями: для них нельзя гарантированно назначить ни конструкторов, ни деструкторов. Возможно, я чего-то тут не знаю.
Другой вопрос - это возникновение исключения, при котором финализация может быть отложена на неопределённый срок. Однако, следует чётко понимать, что исключение - это не правило. Если возник фатальный сбой, то освобождение ресурсов может подождать.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 07:22 

Зарегистрирован: Понедельник, 25 Февраль, 2008 08:42
Сообщения: 125
Иван Кузьмицкий писал(а):
Обработка исключений придумана для того, чтобы подавить аварийную ситуацию и, по возможности, вернуть вычисление в рабочее русло. А вот если проблема вылезет в секции finally, то уже никакие примочки не помогут. Так что лучше придавать себе уверенности жёсткими проверками пред- и постусловий, нежели обёртывать всё и вся в try...except. Лучше, если ошибка проявит себя как можно раньше, а не будет задавлена обработчиком исключения.

Для того и существует секция finally, чтобы сделать финальные действия, которые нужно сделать в любом случае. Естественно, никакие обрадотки тут не ведуться, практически – закрытие файлов, освобождение памяти и тому подобное.

А вы, зачем-то притащили сюда в try...except. Я об этой паре ни слова не сказал.
И скажу честно, у меня в моих программах нет ни одной секции except. Вообще.
А вот try... finally – много.
И именно потому, что проверки я прописываю, но вот всех ситуаций не предусмотришь.
Особенно ситуации, которые возникают не в программе, а за её пределами, в операционке – может кто память пожрал или иные ресурсы кончились.

Иван Кузьмицкий писал(а):
Отработка подавления аварийной ситуации тоже может содержать ошибку, которая как раз вылезет в самый интересный момент :)

Может, если использовать не по назначению.


Евгений Темиргалеев писал(а):
А экскепшен может произойти в любой момент - типа той же нехватки памяти - в том месте, где вы финалли не предусмотрели. Или было лень написать. Или забыли про какой-то ресурс.

В КП память собирает сборщик мусора (100% гарантия сборки, программист ничего не пишет для этого); прочие ресурсы типа файлов закрываются в FINALIZE (программист реализует метод; вызовов не пишет; гарантия освобождения 100%).


Я вызвал процедуру.
В ней локальная переменная типа TQuery.
Мне нужно сделать запрос к базе и т.д.
В итоге, я обращаюсь к внешнему ресурсу – к операционной системе, для получения ресурса.
Получил, тут бац и что-то меня выбросило.
В секции finally я спокойно освободил внешние ресурсы и вышел из процедуры.
Кстати, проверить, вышел я аварийно или нет тоже не вопрос, если это важно для вызывающего процесса.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 08:32 

Зарегистрирован: Вторник, 18 Сентябрь, 2007 08:48
Сообщения: 108
В Обероне ошибки внутри программных модулей легко локализуются и пропалываются. При условии, что варится внутренняя кухня без обращения к внешней среде. В этом случае можно обойтись одними Asserta'ми.
В случае же взаимодействия со внешней средой - все сложнее, тут действует правило - "лучше перебдеть, чем недобдеть" - лучше предупредить юзера о внештатной ситуации, а не поставить его перед свершившимся фактом. К примеру, вызываю COM-объект, а его нет - юзер прибил dll.
Есть случаи, когда задача крутиться в цикле, и её нежелательно останавливать из-за возникшего трапа, лучше подавить с выдачей в лог или предупреждением.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 08:41 

Зарегистрирован: Вторник, 18 Сентябрь, 2007 08:48
Сообщения: 108
Если мне приспичится написать на ББ приложение средствами WinAPI - то я буду практически беззащитен. Оно мне надо?


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 08:53 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
Александр Ильин писал(а):
Компонентному Паскалю конструкторы не нужны в связи с наличием ABSTRACT'ных записей.
Я пока не понял этот аргумент.
В Си++ тоже есть абстрактные классы (классы с чисто виртуальными функциями нельзя инстанцировать), однако потребность в конструкторах это не отменяет.
Вероятно, Вы имеете в виду, что в таком случае конструктор можно заменить фабричной функцией. (Что, кстати, можно приравнять к виртуальному конструктору. Это бонус.)
Но фабричная функция в КП имеет один недостаток: новый объект может быть создан только в динамической памяти. Но ведь Оберон/КП не Java?

Александр Ильин писал(а):
Есть, правда, неопределённость со стековыми записями: для них нельзя гарантированно назначить ни конструкторов, ни деструкторов. Возможно, я чего-то тут не знаю.
Смутно вспоминаю некоторые расширения в Object Oberon.
Интересно, нельзя ли расширить язык подобной конструкцией?
Код:
RECORD
    n: INTEGER;
    f:  Files.File;
BEGIN
    n := 0;
    (* инициализировать f необязательно, т.к. указатели и так инициализируются NIL *)
CLOSE
    IF (f # NIL) & f.isOpen() THEN
        f.Close
    END
END;
IMHO, синтаксически это вполне в духе языка, новых ключевых слов не надо.
Главная мысль: обеспечить гарантированную минимальную инициализацию (т.е. это не совсем конструктор, т.к. без параметров), а также финализацию объектов не только в динамической памяти.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 09:18 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
Другой вариант: расширить синтакс процедуры.
Код:
PROCEDURE TramPamPam;
  VAR f: Files.File;
BEGIN
  ...
CLOSE (* аналог finally *)
  IF (f # NIL) & f.isOpen() THEN f.Close END
END TramPamPam;
Этот вариант может оказаться more convenient, т.к. не порождает вопросы с наследованием, которые могли бы возникнуть применительно к записям-классам.
Обращаю внимание на сугубую опциональность секции CLOSE. Её имеет смысл использовать только для освобождения управляемых ресурсов (вроде файлов и т.п.). Проблем с освобождением памяти (как в Си++) в Обероне нет.


Последний раз редактировалось AVC Четверг, 23 Октябрь, 2008 09:44, всего редактировалось 1 раз.

Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 09:43 

Зарегистрирован: Понедельник, 28 Ноябрь, 2005 10:28
Сообщения: 1202
Александр Ильин писал(а):
Kernel.Try и Kernel.TrapHandler - пожалуйста. Аналогичным образом можно реализовать любую работу с исключениями внутри BlackBox - библиотечными средствами. Тема, я так понимаю, о том, надо ли это всё в языке. Я по-прежнему считаю, что нет.


Проблема в том, что
Код:
TryHandler* = PROCEDURE (a, b, c: INTEGER);
PROCEDURE Try* (h: TryHandler; a, b, c: INTEGER);

и чтобы обрабатывать исключения в процедуре, её надо приводить к виду
Код:
PROCEDURE MyProc(a, b, c: INTEGER);

Возможно в языке нужны другие средства, которые бы позволили сделать удобные библиотечные средства.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 10:03 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2316
Откуда: Россия, Томск
AVC писал(а):
Александр Ильин писал(а):
Компонентному Паскалю конструкторы не нужны в связи с наличием ABSTRACT'ных записей.
Я пока не понял этот аргумент... Вероятно, Вы имеете в виду, что в таком случае конструктор можно заменить фабричной функцией.
Именно так.
AVC писал(а):
Но фабричная функция в КП имеет один недостаток: новый объект может быть создан только в динамической памяти. Но ведь Оберон/КП не Java?
Мне представляется, что стековые объекты имеют ограниченный срок жизни и сферу применения, и в этой связи не нуждаются в дополнительной автоматике. Если нужна инициализация-финализация - пользуйтесь динамической памятью (на то сборка мусора и нужна, чтобы не бояться этого варианта). Единственный недостаток - это накладные расходы на выделение и освобождение памяти. Такие расходы при интенсивном использовании могут действительно сильно нагружать при некой монотонной работе (в цикле или с постоянно запрашиваемым ресурсом). Например, у меня был случай, когда я использовал объект Context для отрисовки в ответ на WM_PAINT. Если у вас такая ситуация, то никто не запрещает кэшировать и повторно использовать объекты кучи. Для этого есть специальный формат фабричной функции, например: Files.File.NewReader (old: Reader): Reader. Если у вас есть кэшированный Reader, то вам его просто заново проинициализируют, без повторного выделения памяти.
AVC писал(а):
Интересно, нельзя ли расширить язык подобной конструкцией?
Код:
RECORD
...
CLOSE
    IF (f # NIL) & f.isOpen() THEN
        f.Close
    END
END;
IMHO, синтаксически это вполне в духе языка, новых ключевых слов не надо.
На момент описания деструктора методы объекта могут быть не известны (если не унаследованы, то находятся ниже по тексту программы).

Новых ключевых слов нет, но... Одна из ключевых особенностей языка - для меня - это отсутствие неявных вызовов. Если в одном случае я пишу NEW и получаю блок памяти, а в другом случае вызывается туча конструкторов, то я начинаю терять контроль над ситуацией. Это то же самое, что перегрузка оператора. Аналогично для стековых переменных: я вызываю процедуру, а на самом деле сначала выполняется десяток конструкторов для её локальных переменных, и я никак не могу на это повлиять. После конструкторов процедура первой же проверкой обнаруживает, что x1>x2, вызывает RETURN и вызывается куча деструкторов. Раз - и куда-то делась вся эффективность.

По концептуальной чистоте фабричные функции гораздо лучше. Есть процедура, есть её явный вызов, поиск в исходнике сразу показывает, что и почему она делает. Без всяких скрытых механизмов.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 10:06 
Аватара пользователя

Зарегистрирован: Пятница, 25 Ноябрь, 2005 12:02
Сообщения: 8185
Откуда: Троицк, Москва
К уже сказанному про BEGIN\CLOSE в RECORD: скорее всего, в этих секциях придется вызывать процедуры, определяемые ниже по тексту. При однопроходности компилятора это может быть проблематично. Повлечет хвост проблем с языком и компилятором.

Trurl: а какие именно изменения предполгаются, примерно?


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 10:19 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
Александр Ильин писал(а):
Единственный недостаток - это накладные расходы на выделение и освобождение памяти. <...> Если у вас такая ситуация, то никто не запрещает кэшировать и повторно использовать объекты кучи.
Да, кэширование в нашем случае (Оберон/КП) - хороший прием.
Его обязательно надо иметь в виду. (Наверное, возможны и какие-то иные его формы.)

Александр Ильин писал(а):
Одна из ключевых особенностей языка - для меня - это отсутствие неявных вызовов. Если в одном случае я пишу NEW и получаю блок памяти, а в другом случае вызывается туча конструкторов, то я начинаю терять контроль над ситуацией. Это то же самое, что перегрузка оператора. Аналогично для стековых переменных: я вызываю процедуру, а на самом деле сначала выполняется десяток конструкторов для её локальных переменных, и я никак не могу на это повлиять. После конструкторов процедура первой же проверкой обнаруживает, что x1>x2, вызывает RETURN и вызывается куча деструкторов. Раз - и куда-то делась вся эффективность.

По концептуальной чистоте фабричные функции гораздо лучше. Есть процедура, есть её явный вызов, поиск в исходнике сразу показывает, что и почему она делает. Без всяких скрытых механизмов.
Это бесспорное достоинство Оберона. (Конечно, иной раз случается, что хорошо бы иметь какие-то возможности иных языков, в т.ч. Си++. Как правило, это можно пережить, а вот контроль над ситуацией - значительная ценность.)
Чтобы не связываться с запутанными механизмами, пока предлагаю только секцию CLOSE в процедурах.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 10:34 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
Info21 писал(а):
К уже сказанному про BEGIN\CLOSE в RECORD: скорее всего, в этих секциях придется вызывать процедуры, определяемые ниже по тексту. При однопроходности компилятора это может быть проблематично. Повлечет хвост проблем с языком и компилятором.
Вполне возможно.
Кроме того, наследование записей может вызвать дополнительные проблемы (может потребоваться уже целая цепочка вызовов BEGIN при инициализации объекта и CLOSE - при его финализации).
Учитывая эти факторы (могут быть и другие), пока ограничиваю свою "инициативу" процедурной секцией CLOSE. Её реализация не должна представить особых трудностей. Код CLOSE должен вызываться при выходе из процедуры (в т.ч. при любом RETURN), а также в случае исключения (это тоже не проблема, т.к. отладчик, например, прекрасно находит всю информацию о процедурах в стеке).
Сейчас вижу 2 основных назначения секции CLOSE:
  • своевременный возврат управляемых ресурсов;
  • возможность перезапуска процесса, который должен работать "24x7", хотя бы и с помощью возможностей Services.
Еще, теоретически, - возможность перехватить исключение и продолжить выполнение процедуры (или начать заново). Может потребоваться новое ключевое слово RETRY (как в XDS).


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 10:51 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2316
Откуда: Россия, Томск
Это именно аналог finally, который, по моему мнению, можно реализовать библиотечно без правки языка. А если можно не править язык, то лучше его не править, по моему мнению. Единственное направление правки, которое я поддерживаю, - это повышение строгости.

В целом признаю, что идея интересная и, возможно, удачная. Возможно введение секции CLOSE оправданно. Ведь ввели же её для модулей, хотя там тоже нет проблем библиотекой отделаться: простенький интерфейс к загрузчику, регистрация финализатора.

В порядке конструктивной критики обращаю ваше внимание на то, что внутри секции CLOSE следует запретить использование RETURN. По крайней мере для процедур, возвращающих какое-то значение.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 10:54 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2316
Откуда: Россия, Томск
Trurl писал(а):
Проблема в том, что
Код:
TryHandler* = PROCEDURE (a, b, c: INTEGER);
PROCEDURE Try* (h: TryHandler; a, b, c: INTEGER);
и чтобы обрабатывать исключения в процедуре, её надо приводить к виду
Код:
PROCEDURE MyProc(a, b, c: INTEGER);
Возможно в языке нужны другие средства, которые бы позволили сделать удобные библиотечные средства.
Не вижу большой проблемы сделать обёртку, которая вместо TryHandler будет брать PROCEDURE (VAR msg: Exception). Правда, практического опыта у меня нет, там могут быть и свои подводные камни.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 11:04 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
Александр Ильин писал(а):
В целом признаю, что идея интересная и, возможно, удачная. Возможно введение секции CLOSE оправданно. Ведь ввели же её для модулей, хотя там тоже нет проблем библиотекой отделаться: простенький интерфейс к загрузчику, регистрация финализатора.
Из модулей КП эта идея и "спёрта". :)
Любопытно, что "деструкторы" здесь применяются не к объектам (что может порожать проблемы, в т.ч. с наследованием), а к модулям и процедурам, у которых как раз проблем с наследованием нет.

Александр Ильин писал(а):
В порядке конструктивной критики обращаю ваше внимание на то, что внутри секции CLOSE следует запретить использование RETURN. По крайней мере для процедур, возвращающих какое-то значение.
Это разумная мысль.
Но, возможно, еще лучше как раз разрешить RETURN (например, со значением, сигнализирующем об ошибке). Такой RETURN в секции CLOSE остановит обработку исключения (если RETURN в секции CLOSE не сработал, то продолжится "свертка" стека) и приведет к "обычному" выходу из функции/процедуры. Так сделано в XDS (с включенной опцией O2ADDKWD).
Тогда, кстати, и дополнительное слово RETRY вовсе не понадобится (как реализующее слишком экзотическую возможность).


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 11:14 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
Немного глуповатый пример, взятый, кажется, из мануала XDS (если не ошибаюсь).
Код:
PROCEDURE DivBy (x, y: INTEGER);
BEGIN
    RETURN a DIV b
EXCEPT
    RETURN MAX(INTEGER)
END DivBy;
В нашем случае это будет
Код:
PROCEDURE DivBy (x, y: INTEGER);
BEGIN
    RETURN a DIV b
CLOSE
    RETURN MAX(INTEGER)
END DivBy;
Я не говорю, что CLOSE стоит использовать именно так, а только хочу проиллюстрировать, как можно использовать RETURN в секции CLOSE.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 11:17 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2316
Откуда: Россия, Томск
AVC писал(а):
Александр Ильин писал(а):
В порядке конструктивной критики обращаю ваше внимание на то, что внутри секции CLOSE следует запретить использование RETURN. По крайней мере для процедур, возвращающих какое-то значение.
Это разумная мысль.
Но, возможно, еще лучше как раз разрешить RETURN (например, со значением, сигнализирующем об ошибке).
А как будет работать и читаться вот такой код:
Код:
PROCEDURE Zzz (a, b: INTEGER): INTEGER;
BEGIN
   RETURN a
CLOSE
   RETURN b
END Zzz;
Не забывайте, что мы говорим о finally, а не об except (или я не прав?). Finally выполняется всегда, и переопределение выходного значения с случае нормальной работы выглядит по меньшей мере странно. Вернуть код ошибки не получится, поскольку исключение не подавлено.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 11:35 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
Александр Ильин писал(а):
Не забывайте, что мы говорим о finally, а не об except (или я не прав?). Finally выполняется всегда, и переопределение выходного значения с случае нормальной работы выглядит по меньшей мере странно.
Согласен. Я именно забыл на минуту о том, что у нас finally, а не catch/except.
В таком случае, действительно, надо запретить RETURN в секции CLOSE.
Но хорошо бы ввести еще секцию EXCEPT :) (название пока не важно), чтобы можно было с помощью RETURN обработать исключение.
Порядок следования секций BEGIN ... EXCEPT ... CLOSE, причем две последние секции необязательны (опциональны).
Впрочем, возможны и какие-то альтернативы введению секции EXCEPT.
Навскидку.
  1. Объявлять функцию с заданным значением на случай исключения: PROCEDURE DivBy(x, y: INTEGER): INTEGER = MAX(INT);
  2. Проверять (в случае нужды) в секции CLOSE, попали мы в нее "легальным" способом или в результате исключения.
И т.д.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 11:52 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
Промежуточный итог обсуждения, IMHO, выглядит так.
  • Путем совсем небольшого изменения языка, не вводя новых ключевых слов и даже понятий (т.к. слово и понятие CLOSE заимствовано из самого КП), можно обеспечить своевременное возвращение управляемых ресурсов.
  • Возможно, Оберону не нужны конструкторы/деструкторы в классическом виде: соответствующие обязанности можно просто возложить на модули и процедуры.
А детали можно уточнить еще не один раз.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 12:01 
Аватара пользователя

Зарегистрирован: Суббота, 19 Ноябрь, 2005 15:59
Сообщения: 803
Откуда: Зеленоград
AVC писал(а):
Впрочем, возможны и какие-то альтернативы введению секции EXCEPT.
Навскидку.
  1. Объявлять функцию с заданным значением на случай исключения: PROCEDURE DivBy(x, y: INTEGER): INTEGER = MAX(INT);
  2. Проверять (в случае нужды) в секции CLOSE, попали мы в нее "легальным" способом или в результате исключения.
И т.д.
Не стоит забывать и о способе обработки исключений, реализованном в ETH Oberon (с использованием метапрограммирования и вложенной процедуры).


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Октябрь, 2008 13:10 

Зарегистрирован: Суббота, 26 Ноябрь, 2005 10:37
Сообщения: 875
Откуда: Россия, Владивосток
Если мне не изменяет память, в GPCP есть секция RESCUE - как раз аналог EXCEPT


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 178 ]  На страницу Пред.  1 ... 3, 4, 5, 6, 7, 8, 9  След.

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 0


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Вся информация, размещаемая участниками на конференции (тексты сообщений, вложения и пр.) © 2005-2019, участники конференции «OberonCore», если специально не оговорено иное.
Администрация не несет ответственности за мнения, стиль и достоверность высказываний участников, равно как и за безопасность материалов, предоставляемых участниками во вложениях.
Без разрешения участников и ссылки на конференцию «OberonCore» любое воспроизведение и/или копирование высказываний полностью и/или по частям запрещено.
Powered by phpBB® Forum Software © phpBB Group
Русская поддержка phpBB