FreeBasic
Вы хотите отреагировать на этот пост ? Создайте аккаунт всего в несколько кликов или войдите на форум.

ковыряем компилятор

Участников: 2

Перейти вниз

ковыряем компилятор Empty ковыряем компилятор

Сообщение  electrik Вт Сен 25, 2012 11:07 pm

предлагаю, в этой теме разбираться с компилятором.
компилируем компилятор, пока под win32.
это понравится тем, кто не любит всякие навороченные make файлы.
оговорюсь сразу, что мы будем компилировать пока только компилятор без RTL библиотек.
потом и rtl закомпилим.
предположим, что вы хотите изменить поведение компилятора, а функции из rtl библиотеки трогать не собираетесь, или как я- пока не собираетесь.
вот вам простой cmd файлик Win32FbcOnly.cmd

Код:

set binpath=bin\
set compilerpath=src\compiler\
set compilerobjpath=src\compiler\obj\

fbc -m fbc %compilerpath%*.bas -e -maxerr 1 -w pedantic -d ENABLE_STANDALONE -d ENABLE_FBBFD=217 %compilerobjpath%bfd-wrapper.o -l bfd -l iberty -l intl -l user32 -x %binpath%fbc.exe
pause

необходимый файл, который я откомпилил на mingw:
http://svalka-spb.narod.ru/progs/bfd-wrapper.o
качаем с оф сайта исходники в zip или tar архиве.
в корень распакованных исходников кидаем файл win32FbcOnly.cmd.
в корне с исходниками создадим папку "bin".
далее "\src\compiler", там создадим папку "obj" и забросим в нее файл bfd-wrapper.o.
перед запуском cmd файла, необходимо, чтоб в переменных окружения был прописан путь к установленному FreeBasic.
если лень куда-то лазить, в начале нашего cmd файла можно написать следующую строку:
Код:

set path=d:\FreeBasic;%path%
путь свой подставите.
запустим наш cmd файл. ждём... на моем двух-ядернике, секунд 30 компилится.
в папке "bin" получаем файл fbc.exe.
почему-то он на несколько сотен байт больше.
может из-за bfd-wrapper'а, я его компилил на последней версии mingw. фиг знает, на какой версии компилят на оф сайте.
ну теперь, можно ковырять компилятор!
я уже начал, и думаю, что потихооонечку врубаюсь.
надеюсь, что скоро начнём оптимизировать цикл while.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Вернуться к началу Перейти вниз

ковыряем компилятор Empty Re: ковыряем компилятор

Сообщение  trew Ср Сен 26, 2012 6:28 am

Серега, а что это за враппер bfd-wrapper.o ?

Что это такое и зачем?

А вообще молодец.

----------------------------------

Я пытался по быстрому понять почему Name не работает с кирилицей, но судя по всему по быстрому не выйдет Very Happy

По сути это должна быть обертка сишного rename.
rename работает нормально, видать где-то по внутренних делам коверкаются символы. Тот же трабл и с RTRIM , LTRIM и TRIM

trew

Сообщения : 331
Дата регистрации : 2010-10-14

Вернуться к началу Перейти вниз

ковыряем компилятор Empty Re: ковыряем компилятор

Сообщение  electrik Чт Сен 27, 2012 1:16 am

привет. фиг знает для чего нужен bfd-wrapper. я в make файле поглядел, вродь под win32 нужен.
да и когда компилишь прогу с ключиком -v, выдается следующее:
standalone, objinfo (libbfd 217)
а вообще эта штука расшифровывается как "Binary File Descriptor".
поповоду кирилицы, может досовская нормально будет работать.
ковырялся, есть функции удаления, создания каталога, а вот name пока не нашел. естественно она есть, только спрятана где-то.
я бы на их месте делал так, если win32,, юзаем напрямую апи.
некоторые функции у них так сделаны. например функция dir, ищет через win32api, если линукс, то через его сервисы.
похоже, что в библиотеках c, нет функций поиска файла, только открытие. я тут тоже хочу замутить свои говорящие часики на мульти платформенность, и хотел использовать во FreeBasic c функции, вот нет такой функции которая бы искала например:
find("*.bas)
вообще в чистом c все развивается очень медленно. вроде в 2012 году были какие-то поправки стандартов, и только в c++ появились такие функции о которых я говорю. это не сам видел, друган сказал.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Вернуться к началу Перейти вниз

ковыряем компилятор Empty Re: ковыряем компилятор

Сообщение  electrik Вс Окт 07, 2012 1:34 am

сегодня мы чуток попробуем исследовать компилятор. попытаемся найти самое простое, цикл while...wend. пока не будем ни чего оптимизировать, просто поймем как вообще устроено внутреннее устройство компилятора, и от стартовой функции компилятора проследуем до функций, которые создают этот цикл.
далеко не всегда просто, проследовать до той или иной функции, смотря лишь на вызываемые include файлы. приходится пересматривать кучу файлов, чтобы найти нужное.

и так, откроем файл fbc.bas, и найдем 2617 строку, далее фрагмент кода, он же является стартовой точкой всего компилятора:
Код:

   fbcInit()

   if (__FB_ARGC__ = 1) then
      printOptions( )
      fbcEnd( 1 )
   end if

   parseArgs(__FB_ARGC__, __FB_ARGV__)

   if (fbc.showversion) then
      printVersion()
      fbcEnd( 0 )
   end if

   if( (listGetHead(@fbc.modules) = NULL) and _
       (listGetHead(@fbc.objlist) = NULL) and _
       (listGetHead(@fbc.libs.list) = NULL)) then
      printOptions()
      fbcEnd( 1 )
   end if

   if (fbc.verbose) then
      printVersion()
   end if

   fbcInit2()

   '' Compile into temporary files

   compileModules()

вот именно самая последняя строчка "compileModules" нам и нужна.
в этом же файле на 2031 строке начинается процедура compileModules
Код:

private sub compileModules()
   dim as integer ismain = FALSE
   dim as integer checkmain = any

   select case fbGetOption( FB_COMPOPT_OUTTYPE )
   case FB_OUTTYPE_EXECUTABLE, FB_OUTTYPE_DYNAMICLIB
      checkmain = TRUE
   case else
      '' When building an object or a library (-c/-r, -lib), nothing
      '' is compiled with ismain = TRUE until -m was given for it.
      '' This makes sense because -c is usually used to compile
      '' single modules of which only a very specific one is the
      '' main one (nobody would want -c to include main() everywhere),
      '' and because -lib is for making libraries which generally
      '' don't include a main module for programs to use.
      checkmain = fbc.mainset
   end select

   dim as string mainfile
   if (checkmain) then
      '' Note: This causes the path given with -m to be ignored in
      '' the ismain check below. This is good because -m is easier
      '' to use that way (e.g. fbc ../../main.bas -m main), and bad
      '' because then modules with the same name but in different
      '' directories will both be seen as the main one.
      mainfile = hStripPath(fbc.mainname)
   end if

   dim as FBCIOFILE ptr module = listGetHead(@fbc.modules)
   while (module)
      if (checkmain) then
         ismain = (mainfile = hStripPath(hStripExt(module->srcfile)))
         '' Note: checking continues for all modules, because
         '' "the" main module could be passed multiple times,
         '' and it makes sense to always treat it the same,
         '' so that <fbc 1.bas 1.bas -c> generates the same 1.o
         '' twice and <fbc 1.bas 1.bas> causes a duplicated
         '' definition of main().
         /'checkmain = not ismain'/
      end if

      compileBas(module, ismain)

      module = listGetNext(module)
   wend

   '' Make sure to add libs from command line to final lists if no input
   '' .bas were given
   if (module = NULL) then
      strsetCopy(@fbc.finallibs, @fbc.libs)
      strsetCopy(@fbc.finallibpaths, @fbc.libpaths)
   end if
end sub

опа, есть строчка " compileBas(module, ismain)". вызывается еще одна процедура в этом файле. начинается на 1969 строке:
Код:

private sub compileBas(byval module as FBCIOFILE ptr, byval ismain as integer)
   '' *.o name based on input file name unless given via -o <file>
   if (len(module->objfile) = 0) then
      module->objfile = hStripExt(module->srcfile) & ".o"
   end if

   dim as string asmfile = getModuleAsmName(module)
   if (fbc.preserveasm = FALSE) then
      fbcAddTemp(asmfile)
   end if

   if (fbc.verbose) then
      print "compiling: ", module->srcfile; " -o "; asmfile;
      if (ismain) then
         print " (main module)";
      end if
      print
   end if

   '' preserve orginal lang id, we may have to restore it.
   dim as FB_LANG prevlangid = fbGetOption(FB_COMPOPT_LANG)

   dim as integer restarts = 0

   do
      '' init the parser
      fbInit(ismain, restarts)

      '' add the libs and paths passed in the cmd-line, so the
      '' compiler can add them to the module's objinfo section
      fbSetLibs(@fbc.libs, @fbc.libpaths)

      fbCompile(module->srcfile, asmfile, ismain)

      '' If there were any errors during parsing, just exit without
      '' doing anything else.
      if (errGetCount() > 0) then
         fbcEnd(1)
      end if

      '' Don't restart unless asked for
      if (fbShouldRestart() = FALSE) then
         exit do
      end if

      '' Restart
      restarts += 1

      '' Shutdown the parser before restarting
      fbEnd()
   loop

   '' Update the list of libs and paths, with the ones found when parsing
   fbGetLibs(@fbc.finallibs, @fbc.finallibpaths)

   '' Shutdown the parser
   fbEnd()

   '' Restore original lang
   fbSetOption( FB_COMPOPT_LANG, prevlangid )
end sub
дааа, муторно, не интересно, но мы пойдем дальше.
в этой процедуре есть следующий вызов:
fbCompile(module->srcfile, asmfile, ismain)
все, закрываем файл fbc.bas и открываем файл fb.bas
на 643 строке начинается процедура fbCompile
Код:

sub fbCompile _
   ( _
      byval infname as zstring ptr, _
      byval outfname as zstring ptr, _
      byval ismain as integer _
   )

   dim as double tmr

   env.inf.name = *infname
   hReplaceSlash( env.inf.name, asc( FB_HOST_PATHDIV ) )
   env.inf.incfile   = NULL
   env.inf.ismain = ismain

   env.outf.name = *outfname
   env.outf.ismain = ismain

   '' open source file
   if( hFileExists( *infname ) = FALSE ) then
      errReportEx( FB_ERRMSG_FILENOTFOUND, infname, -1 )
      exit sub
   end if

   env.inf.num = freefile
   if( open( *infname, for binary, access read, as #env.inf.num ) <> 0 ) then
      errReportEx( FB_ERRMSG_FILEACCESSERROR, infname, -1 )
      exit sub
   end if

   env.inf.format = hCheckFileFormat( env.inf.num )

   ''
   if( irEmitBegin( ) = FALSE ) then
      errReportEx( FB_ERRMSG_FILEACCESSERROR, env.outf.name, -1 )
      exit sub
   end if

   if( fbGetOption( FB_COMPOPT_PPONLY ) ) then
      env.ppfile_num = freefile()
      dim as string ppfile = hStripExt( env.inf.name ) + ".pp.bas"
      if( open( ppfile, for output, as #env.ppfile_num ) <> 0 ) then
         errReportEx( FB_ERRMSG_FILEACCESSERROR, ppfile, -1 )
         exit sub
      end if
   else
      env.ppfile_num = 0
   end if

   fbMainBegin( )

   tmr = timer( )

   fbParsePreDefines()
   fbParsePreIncludes()
   if (fbShouldContinue()) then
      cProgram()
   end if

   tmr = timer( ) - tmr

   fbMainEnd( )

   '' save
   irEmitEnd( tmr )

   if( env.ppfile_num > 0 ) then
      close #env.ppfile_num
   end if

   close #env.inf.num

   '' check if any label undefined was used
   if (fbShouldContinue()) then
      symbCheckLabels(symbGetGlobalTbHead())
   end if
end sub

обратим внимание на фрагмент кода:
Код:

   tmr = timer( )

   fbParsePreDefines()
   fbParsePreIncludes()
   if (fbShouldContinue()) then
      cProgram()
   end if

   tmr = timer( ) - tmr

в начале и в конце фрагмента, идет проверка таймера. за чем? думаю, что для определения скорости компиляции программы - не в бинарник.
смотрим фрагмент кода:
Код:

   if (fbShouldContinue()) then
      cProgram()
   end if
вызов очередной процедуры cProgram
закрываем файл fb.bas и открываем файл parser-toplevel.bas.
продолжение следует.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Вернуться к началу Перейти вниз

ковыряем компилятор Empty Re: ковыряем компилятор

Сообщение  electrik Вс Окт 07, 2012 1:35 am

на 86 строке начинается процедура cProgram:
Код:

sub cProgram()
   dim as integer startlevel = pp.level

   '' For each line...
   do
      parser.stmt.cnt += 1

      '' line begin
      astAdd( astNewDBG( AST_OP_DBG_LINEINI, lexLineNum( ) ) )

      dim as ASTNODE ptr proc = astGetProc( ), expr = astGetProcTailNode( )

      '' Label?
      cLabel( )

      '' Statement?
      cStatement( )

      '' Comment?
      cComment( )

      if (fbShouldContinue() = FALSE) then
         exit sub
      end if

      '' emit the current line in text form
      if( env.clopt.debug ) then
         if( env.includerec = 0 ) then
            '' don't add if proc changed (from main() to proc block or the inverse)
            if( proc = astGetProc( ) ) then
               astAddAfter( astNewLIT( lexCurrLineGet( ) ), expr )
            end if
            lexCurrLineReset( )
         end if
      end if

      select case (lexGetToken())
      case FB_TK_EOL
         lexSkipToken( )

      case FB_TK_EOF

      case else
         errReport( FB_ERRMSG_EXPECTEDEOL )
         '' error recovery: skip until EOL
         hSkipUntil( FB_TK_EOL, TRUE )
      end select

      if (fbShouldContinue() = FALSE) then
         exit sub
      end if

      '' line end
      astAdd( astNewDBG( AST_OP_DBG_LINEEND ) )
   loop while (lexGetToken() <> FB_TK_EOF)

   '' EOF
   assert(lexGetToken() = FB_TK_EOF)

   parser.stmt.cnt += 1

   if (pp.level <> startlevel) then '' inside #IF block?
      errReport( FB_ERRMSG_EXPECTEDPPENDIF )
   end if

   lexSkipToken()

   '' only check compound stmts if not parsing an include file
   if (env.includerec = 0) then
      cCompStmtCheck()
   end if
end sub

смотрим фрагмент кода:
Код:

      '' метка?
      cLabel( )

      '' оператор?
      cStatement( )

      '' комментарий?
      cComment( )

вот именно cStatement нам и нужен.
закрываем файл parser-toplevel.bas и открываем файл parser-statement.bas
на 19 строке начинается процедура cStatement:
Код:

sub cStatement()
   '' ':'?
   if( lexGetToken( ) = FB_TK_STMTSEP ) then
      parser.stmt.cnt += 1
      lexSkipToken( )
   end if

   do
      if( cDeclaration( ) = FALSE ) then
         if( cCompoundStmt( ) = FALSE ) then
            if( cProcCallOrAssign( ) = FALSE ) then
               if( cQuirkStmt( ) = FALSE ) then
                  if( cAsmBlock( ) = FALSE ) then
                     cAssignmentOrPtrCall( )
                  end if
               end if
            end if
         end if
      end if

      parser.stmt.cnt += 1

      '' ':'?
      if( lexGetToken( ) <> FB_TK_STMTSEP ) then
         exit do
      end if
      lexSkipToken( )
   loop
end sub

смотрим фрагмент:
Код:

      if( cDeclaration( ) = FALSE ) then
         if( cCompoundStmt( ) = FALSE ) then
            if( cProcCallOrAssign( ) = FALSE ) then
               if( cQuirkStmt( ) = FALSE ) then
                  if( cAsmBlock( ) = FALSE ) then
                     cAssignmentOrPtrCall( )
                  end if
               end if
            end if
         end if
      end if

нам нужна та функция которая проверяется в условии:
if( cCompoundStmt( ) = FALSE ) then
эта функция cCompoundStmt.
закрываем файл parser-statement.bas и открываем файл parser-compound.bas.
начинается самое интересное. на 68 строке начинается магическая функция cCompoundStmt:

Код:

function cCompoundStmt as integer

   function = FALSE

    '' QB mode?
    if( env.clopt.lang = FB_LANG_QB ) then
       if( lexGetType() <> FB_DATATYPE_INVALID ) then
          return FALSE
       end if
    end if

   select case as const lexGetToken( )
   case FB_TK_IF
      CHECK_CODEMASK( FB_TK_IF, FB_TK_IF )
      cIfStmtBegin()
      function = TRUE

   case FB_TK_FOR
      CHECK_CODEMASK( FB_TK_FOR, FB_TK_NEXT )
      function = cForStmtBegin( )

   case FB_TK_DO
      CHECK_CODEMASK( FB_TK_DO, FB_TK_LOOP )
      cDoStmtBegin()
      function = TRUE

   case FB_TK_WHILE
      CHECK_CODEMASK( FB_TK_WHILE, FB_TK_WEND )
      cWhileStmtBegin()
      function = TRUE

   case FB_TK_SELECT
      CHECK_CODEMASK( FB_TK_SELECT, FB_TK_SELECT )
      cSelectStmtBegin()
      function = TRUE

   case FB_TK_WITH
      CHECK_CODEMASK( FB_TK_WITH, FB_TK_WITH )
      cWithStmtBegin()
      function = TRUE

   case FB_TK_SCOPE
      CHECK_CODEMASK( FB_TK_SCOPE, FB_TK_SCOPE )
      function = cScopeStmtBegin( )

   case FB_TK_NAMESPACE
      function = cNamespaceStmtBegin( )

   case FB_TK_EXTERN
      function = cExternStmtBegin( )

   case FB_TK_ELSE, FB_TK_ELSEIF
      function = cIfStmtNext( )

   case FB_TK_CASE
      function = cSelectStmtNext( )

   case FB_TK_LOOP
      function = cDoStmtEnd( )

   case FB_TK_NEXT
      function = cForStmtEnd( )

   case FB_TK_WEND
      function = cWhileStmtEnd( )

   case FB_TK_EXIT
      cExitStatement()
      function = TRUE

   case FB_TK_CONTINUE
      cContinueStatement()
      function = TRUE

   case FB_TK_END
      '' any compound END will be parsed by the compound stmt
      if( lexGetLookAheadClass( 1 ) <> FB_TKCLASS_KEYWORD ) then
         CHECK_CODEMASK( INVALID, INVALID )
         return cEndStatement( )
      end if

      function = cCompoundEnd( )

   case FB_TK_ENDIF
      function = cIfStmtEnd( )

   case FB_TK_USING
      function = cUsingStmt( )

   case else
      return FALSE
   end select

end function

как мы уже поняли, проверяются токены, for, next, while, wend, do, loop и т.д.
как парсер видит такие ключевые слова как while, wend, for, next, он запускает соответствующие функции/процедуры, которые проверяют синтаксис, выражения, типы данных,а затем генерируют код.
нам нужна одна функция и одна процедура.
фрагменты кода:
Код:

   case FB_TK_WHILE
      CHECK_CODEMASK( FB_TK_WHILE, FB_TK_WEND )
      cWhileStmtBegin()
      function = TRUE

   case ...
...
...
   case FB_TK_WEND
      function = cWhileStmtEnd( )

вот что нам нужно:
cWhileStmtBegin
cWhileStmtEnd

закрываем файл parser-compound.bas и открываем файл parser-compound-while.bas.
продолжение следует.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Вернуться к началу Перейти вниз

ковыряем компилятор Empty Re: ковыряем компилятор

Сообщение  electrik Вс Окт 07, 2012 1:37 am

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

Код:

'':::::
''WhileStmtBegin  =  WHILE выражение .
''
sub cWhileStmtBegin()
    dim as ASTNODE ptr expr = any ' узел expr абстрактного синтаксического дерева, туда будем читать выражение. объявлен в файле ast.bi
    dim as FBSYMBOL ptr il = any, el = any ' метки начала и конца цикла while...wend. тип объявлен в файле symb.bi
    dim as FB_CMPSTMTSTK ptr stk = any ' пока не понял, но вроде что-то внутреннего стека. тип объявлен в файле parser.bi

   '' WHILE
   lexSkipToken( ) ' процедура находится в файле lex.bas

   '' add ini and end labels ' добавление меток начала и конца цикла
   il = symbAddLabel( NULL ) ' процедура находится в файле symb-label.bas
   el = symbAddLabel( NULL, FB_SYMBOPT_NONE ) ' FB_SYMBOPT_NONE объявлено в файле symb.bi, enum FB_SYMBOPT

   '' emit ini label
   astAdd( astNewLABEL( il ) ) ' добавляем в синтаксическое дерево новую метку начала цикла - новый узел. процедура astAdd находится в файле ast-node-proc.bas

   '' Expression - выражение
   expr = cExpression( ) ' парсим выражение
   if( expr = NULL ) then
      errReport( FB_ERRMSG_EXPECTEDEXPRESSION ) ' если нет выражения, ошибка, ожидается выражение
      '' error recovery: fake an expr
      expr = astNewCONSTi( 0, FB_DATATYPE_INTEGER ) ' функция находится  в файле ast-node-const.bas
   end if

   '' branch
   expr = astUpdComp2Branch( expr, el, FALSE ) ' функция находится в файле ast-misc.bas
   if( expr = NULL ) then
      errReport( FB_ERRMSG_INVALIDDATATYPES )
   else
      astAdd( expr ) ' добавляем в синтаксическое дерево выражение
   end if

   '' push to stmt stack '  сохраняется во внутренний стек
   stk = cCompStmtPush( FB_TK_WHILE ) ' функция находится в файле parser-compound.bas
   stk->scopenode = astScopeBegin( ) ' функция находится в файле ast-node-scope.bas
   stk->while.cmplabel = il
   stk->while.endlabel = el
end sub


'':::::
''WhileStmtEnd  =  WEND
''
function cWhileStmtEnd as integer
   dim as FB_CMPSTMTSTK ptr stk = any

   function = FALSE

   stk = cCompStmtGetTOS( FB_TK_WHILE ) ' функция находится в файле parser-compound.bas
   if( stk = NULL ) then
      exit function
   end if

   '' WEND
   lexSkipToken( )

   if( stk->scopenode <> NULL ) then
      astScopeEnd( stk->scopenode )
   end if

    astAdd( astNewBRANCH( AST_OP_JMP, stk->while.cmplabel ) ) ' добавляем в синтаксическое дерево скачок на начало цикла, где идет проверка условия

    '' end label (loop exit)
    astAdd( astNewLABEL( stk->while.endlabel ) )

   '' pop from stmt stack
   cCompStmtPop( stk )

   function = TRUE

end function

напишем простой код:
Код:

dim a as integer = 10
while (a > 0)
wend
поидее, функция cWhileStmtBegin, должна сгенерить следующий код:
Код:

.Lt_0004:
cmp dword ptr [ebp-8], 0
jle .Lt_0005

a функция cWhileStmtEnd следующий код:
Код:

jmp .Lt_0004
.Lt_0005:

вот мы кое как, со скрипом, имеем малое представление о маленькой части компилятора.
давайте думать. если я один буду, думаю, не доживете до того времени, когда я разберусь с компилятором.
как бы хотелось оптимизировать цикл while...wend:
переместить сравнение в конец цикла, а в начале перед телом цикла добавить прыжок на сравнение.
так мы избавимся от лишнего jmp в теле цикла
Код:

jmp .Lt_0005 ' прыжок на метку сравнения
.Lt_0004: метка начала цикла
' тело цикла
.Lt_0005: ' метка с которой начинается сравнение
cmp dword ptr [ebp-8], 0
jg .Lt_0005 ' если условие истино, идем на метку начала цикла
.Lt_0006:
где еще поковырять.
для начала будет достаточно файл parser-compound-do.bas.
там реализованы циклы do...loop.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Вернуться к началу Перейти вниз

Вернуться к началу

- Похожие темы

 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения