ADOLF

WarCraft 3: 5.Макросы

cJass

5.1 Общие понятия

Макросы являются одним из ключевых элементов cJass. Обработка макросов полностью проходит во время трансляции кода (которая осуществляется при сохранении карты), что позволяет добиться высокой наглядности кода без его ненужного усложнения.
Для первого знакомства, процесс обработки макросов можно представить как простую замену имен макросов их значениями (как если бы вы использовали замену в любом текстовом редакторе). При записи макросов используется ключевое слово define:
  define FOOTMAN = 'hfoo'
здесь FOOTMAN является именем макроса, которое вы в дальнейшем можете использовать в коде, а 'hfoo' — значением, на которое заменятся все вызовы этого макроса после обработки парсером. К примеру,
  define FOOTMAN = 'hfoo'
  
  nothing test() {
      CreateUnit(GetLocalPlayer(), FOOTMAN, 0, 0, 0)
  }
будет транслировано в
  function test takes nothing returns nothing
      CreateUnit(GetLocalPlayer(), 'hfoo', 0, 0, 0)
  endfunction
Вы также можете записывать несколько макросов за раз, просто используя define с последующим блоком:
  define {
      FOOTMAN = 'hfoo'
      MAGE = 'Hblm'
  }
Для тех, кто предпочитает стандартную запись команд JASS2, имеется следующий вариант:
  define
      FOOTMAN = 'hfoo'
      MAGE = 'Hblm'
  enddefine
Обычно имена макросов состоят из букв латинского алфавита, цифр и знака подчеркивания, но иногда возникает необходимость использовать для имени макроса специальные символы вроде скобок, кавычек или просто нескольких слов. Для достижения этого, имя макроса нужно заключить в угловые скобки:
  define <GetPlayableMapRect()> = bj_mapInitialPlayableArea
А что, если вам необходимо определить макрос, который заменяется на выражение, имеющее несколько строк? Никаких проблем — просто используйте блок внутри макроса!
  define msg = {
    BJDebugMsg("one!")
    BJDebugMsg("two!")
  }

5.2 Область видимости

Макросы могут быть определены внутри регионов, библиотек, структур и модулей как принадлежащие только этой области видимости с помощью ключевого слова private:
  scope test {
      define private FOOT = 'HBlm'
  }
Тогда они будут иметь данное значение только внутри текущей области видимости и не будут конфликтовать с макросами, имеющими такое же имя, но находящимися вне данной области видимости, например:
  define msg = "X"
  
  nothing test () {
      BJDebugMsg("Global = " + msg)
  }
  
  library A {
      define private msg = "A"
  
      nothing test () {
          BJDebugMsg("Library A = " + msg)
      }
  }
  
  scope B {
      define private msg = "B"
  
      nothing test () {
          BJDebugMsg("Scope B = " + msg)
      }
  }
что после трансляции примет следующий вид:
  function test takes nothing returns nothing
      call BJDebugMsg("Global = " + "X")
  endfunction
  
  library A
      function test takes nothing returns nothing
          call BJDebugMsg("Library A = " + "A")
      endfunction
  endlibrary
  
  scope B
      function test takes nothing returns nothing
          call BJDebugMsg("Scope B = " + "B")
      endfunction
  endscope

5.3 Директивы setdef и undef

При повторном определении макроса в пределах одной области видимости cJass выдаст ошибку о том, что данный макрос уже определен (это не касается приватных макросов во вложенных элементах). Если же вы хотите изменить значение ранее определенного макроса, то для этого стоит использовать директиву setdef. Для отмены назначения макроса используется директива undef, после которой вызов макроса приведет к ошибке во время трансляции.
  nothing test1 () {
      define msg = "text"
  
      BJDebugMsg(msg) // выведет строку "text"
  
      setdef msg = "other text"
  
      BJDebugMsg(msg) // выведет строку "other text"
  
      undef msg
  
      BJDebugMsg(msg) // не будет заменено
  }

5.4 Макросы с аргументами

Для расширения диапазона возможных применений макросов были введены макросы, принимающие аргументы. Макрос может принимать любое количество аргументов, которые используются аналогично аргументам функций. Тип аргумента макроса может быть любым — парсер не производит проверки типов, что с другой стороны является причиной осторожнее относиться к передаваемым в макрос аргументам.
  define msg(playerid,text) = DisplayTextToPlayer(Player(playerid), text, 0, 0)
Внимание! Так как парсер не проверяет типы аргументов макроса, это может приводить к ошибкам по невнимательности. Контроль за значениями, передаваемыми макросам, остаётся на совести программиста.

5.5 Перегрузка макросов

Определение в пределах одной области видимости нескольких макросов с одинаковыми именами не вызовет ошибки, если они отличаются количеством принимаемых аргументов. Такие макросы называются перегружеными. В зависимости от количества переданных при вызове аргументов, будет вызван тот или иной из перегруженных макросов.
  define {
      msg(text)              = DisplayTextToPlayer(GetLocalPlayer(), text, 0, 0)
      msg(text,playerid)     = DisplayTextToPlayer(Player(playerid), text, 0, 0)
      msg(text,playerid,x,y) = DisplayTextToPlayer(Player(playerid), text, x, y)
  }
  
  nothing test() {
      msg("test 1")
      msg("test 2", 1)
      msg("test 3", 2, 0.1, 0.1)
  }
будет транслировано в следующее:
  function test takes nothing returns nothing
      call DisplayTextToPlayer(GetLocalPlayer(), "test 1", 0, 0)
      call DisplayTextToPlayer(Player(1), "test 2", 0, 0)
      call DisplayTextToPlayer(Player(2), "test 3", 0.1, 0.1)
  endfunction
Внимание! Если в перегруженной группе есть макрос, не принимающий аргументов, его все равно необходимо записывать с указанием пустых скобок после него.

5.6 Конструкции, используемые в макросах

Иногда возникает потребность вывести аргумент макроса в текстовом виде (обратите внимание: не значение аргумента, а аргумент). Для этого существует инструкция ``, которая представляет переданный ей аргумент в виде строки. Также в макросах можно использовать оператор конкатенации (склеивания) ##, который склеивает в одно слово выражения, находящиеся с двух сторон от него (этот оператор можно использовать в любом месте кода, а не только внутри макросов).
  define register_func(type) = {
      nothing func_##type (type t) {
          BJDebugMsg(`type`)
      }
  }
  
  register_func(real)
что после трансляции будет выглядеть так:
  function func_real takes real t returns nothing
      call BJDebugMsg("real")
  endfunction

5.7 Предопределённые макросы

Для удобства программиста, cJass имеет несколько заранее определённых макросов, которые могут использоваться при написании кода. Все предопределённые макросы заменяются на свои значения во время трансляции.
DATE — возвращает текущую дату в виде гггг.мм.дд
TIME — возвращает текущее время в виде чч:мм:сс
COUNTER — возвращает целое число начиная с 0, при каждом использовании число увеличивается на 1. Пример использования таков:
  define unique_name = func_##COUNTER
  
  void unique_name () {}   // это станет func_0
  void unique_name () {}   // а это - func_1
  void unique_name () {}   // соответственно тут func_2
DEBUG — возвращает 1 если включен флажок "Debug mode", иначе возвращает ноль. Используется в условной трансляции (см. 6.2) для обозначения действий, выполняемых только в режиме отладки.
FUNCNAME — возврашает имя функции, в которой использован.
WAR3VER — возвращает WAR3VER_23 или WAR3VER_24 в зависимости от положения переключателя в меню cJass. Может использоваться в блоках условной компиляции (см. 6.2) для легкой поддержки двух версий карты: для игры до 1.24 и после. Например:
  #define H2I(h) = GetHandleId(h)
  
  #if WAR3VER == WAR3VER_23
      undef H2I
      integer H2I (handle h) { return h; return 0 }
  #endif
Все предопределённые макросы возвращают не строковое значение. Для представления их в виде строки используйте оператор преобразования в строку или форматированный вывод (см. 8.3)

5.8 Перехват функций

Необходимость выполнять некоторые действия каждый раз при вызове native-функций возникает достаточно часто. В таких случаях вводят вспомогательные функции-обертки и используют их. С помощью макросов cJass вы можете прозрачно заменить вызовы конкретной функции на вызовы другой функции, либо на заданный вами набор операций в макросе.
  define {
      SetUnitPosition = SetUnitPosition_hook
  
      RemoveUnit(u) = {
          BJDebugMsg("A unit is being removed!")
          Remove##Unit(u)
      }
  }
  
  boolean SetUnitPosition_hook (unit u, real x, real y) {
      BJDebugMsg("We're moving the unit to (" + R2S(x) + "," + R2S(y) + ")")
      SetUnit##Position(u, x, y)
      return (GetUnitX(u) == x) && (GetUnitY(u) == y)
  }
  
  nothing test() {
      unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
      if (SetUnitPosition(u, 100, 300)) { BJDebugMsg("Landed successfully!") }
      RemoveUnit(u)
  }
что после трансляции выглядит так:
  function SetUnitPosition_hook takes unit u, real x, real y returns boolean
      call BJDebugMsg("We're moving the unit to (" + R2S(x) + "," + R2S(y) + ")")
      call SetUnitPosition(u, x, y)
      return (GetUnitX(u) == x) and (GetUnitY(u) == y)
  endfunction
  
  function test takes nothing returns nothing
      local unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
      if (SetUnitPosition_hook(u, 100, 300)) then
          call BJDebugMsg("Landed successfully!")
      endif
      call BJDebugMsg("A unit is being removed!")
      call RemoveUnit(u)
  endfunction
Теперь приведу немного пояснений к данному примеру. Для замены функции RemoveUnit на несколько действий мы просто определяем макрос с таким же именем, тогда при обработке карты все вызовы функции будут расценены препроцессором как обращения к данному макросу и будут заменены на его значение. Обратите внимание, что внутри макроса вызов перехватываемой функции записан с использованием оператора склеивания строк. Делается это для того, чтобы парсер не принимал данный вызов функции как вызов макроса и не заменял его при обработке. Для замены функции SetUnitPosition на нужный нам вариант, мы просто определяем функцию-обертку и с помощью макроса заменяем все вызовы оригинальной функции на вызовы функции, введенной нами.

5.9 Эмуляция аргументов функций по умолчанию

Во многих языках аргументам функций можно назначать значения по умолчанию — в таком случае при их вызове эти аргументы можно безнаказанно опускать если вас устраивает значение по умолчанию. Подобное поведение функций можно эмулировать в cJass, используя перегруженные макросы и оператор конкатенации.
  define {
      CreateUnit(p, id) = Create##Unit(p, id, 0, 0, 0)
      CreateUnit(p, id, x, y) = Create##Unit(p, id, x, y, 0)
      CreateUnit(p, id, x, y, f) = Create##Unit(p, id, x, y, f)
  }
  
  nothing test() {
      CreateUnit(Player(0), 'hfoo')
      CreateUnit(Player(1), 'Hblm', 100, 231)
      CreateUnit(Player(2), 'Ewar', 382, 16, 42)
  }
что вполне ожидаемо транслируется в
  function test takes nothing returns nothing
      call CreateUnit(Player(0), 'hfoo', 0, 0, 0)
      call CreateUnit(Player(1), 'Hblm', 100, 231, 0)
      call CreateUnit(Player(2), 'Ewar', 382, 16, 42)
  endfunction

Просмотров: 676

Комментарии пока отсутcтвуют