사용자 도구

사이트 도구


kb:cppidentifierextraction

C++ 상수값 뽑아내기

게임 안의 특정 구성요소를 외부 스크립트로 빼낼 때 약간 골칫거리가 되는 것이 게임 안에서 쓰이는 상수다. 예를 들어 플레이어 인벤토리의 최대 크기라던지, 각종 능력치 최대값 같은 것들 말이다. 스크립트 코드를 작성할 때 이런 상수들을 직접 써주는 방법도 있지만, C/C++ 소스에서 자동으로 뽑아내는 것이 좋다. 에러가 발생할 확률도 줄고, 상수들이 변경되었을 때도 쉽게 처리할 수 있기 때문이다.

이런 작업 자체는 C/C++로 짜기보다는 정규식 처리가 가능한 스크립트 언어를 이용하는 것이 좋다. 완벽한 파서를 만들면 좋겠지만, 딱히 매달릴 필요는 없으며, 원하는 값들만 뽑아낼 수 있을 정도라면, 땜빵이라도 괜찮다고 생각한다.

enum 값 뽑아내기

C++ 소스에서 열거값을 뽑아내어 Lua 소스를 만들어내는 스크립트이다.

파이썬 버전

# -*- coding: euc-kr -*-
# @file extract_enums.py
# @author excel96
# @brief C++ 소스에서 열거값 데이터를 LUA 소스로 뽑아내기 위한 스크립트
 
import re
import string
 
# 문자열에서 C++ identifier를 뽑아낸다.
# "this_is_identifier = 0;" --> "this_is_identifier"
def extract_id(text):
    m = re.search(r"[a-zA-Z_][a-zA-Z0-9_]*", text)
    if m != None:
        return m.group(0)
    return ""
 
# 문자열에서 코멘트 문자열을 뽑아낸다. 
# 원래 라인 주석은 "//" 뒤쪽의 모든 문자열이지만,
# 현재 소스의 특수성(doxygen)을 고려하여 "///<"에 대한 처리도 집어넣었다.
# "// this is comment" --> "this is comment"
# "///< this is comment" --> "this is comment"
def extract_comment(text):
    m = re.search(r"(//[/<]*)(.*)", text)
    if m != None:
        return m.group(2).strip()
    return ""
 
# 주어진 N개의 파일들에서 열거값을 뽑아내어,
# 하나의 출력 파일에다 그 값들을 기록한다.
def generate(input_filenames, output_filename):
    # 출력 파일을 연다.
    output = file(output_filename, "w")
 
    # 입력 파일 목록 모두를 열어서 열거값을 출력 파일에다 기록한다.
    for filename in input_filenames:
        input = file(filename, "r")
        bEnum = 0 # 현재 열거값 내부를 처리 중인가?
        count = 0 # 열거값 처리 중이라면 현재 열거값의 실제 값은?
 
        for line in input.readlines():
            # 열거값 처리 중이라면...
            if bEnum:
                # 열거값 구문의 끝에 도달했는지 체크한다.
                if re.search("^};$", line) != None:
                    output.write("\n")
                    bEnum = 0
                # 열거값 구문의 끝이 아니라면, 열거값과 그 값을 기록한다.
                else:
                    id = extract_id(line)
                    if len(id) > 0:
                        text = id + " = " + str(count)
 
                        # 열거값 뒤쪽에 붙은 주석을 처리한다.
                        comment = extract_comment(line)
                        if len(comment) > 0:
                            text = text + " -- " + comment
 
                        output.write(text + "\n")
                        count = count + 1
            # 열거값 구문이 새로 시작되는지 체크한다.
            elif re.search(r"(^enum)([\t ]+[a-zA-Z_][a-zA-Z0-9_]*)*", line) != None:
                bEnum = 1
                count = 0
 
        input.close()
    output.close()
 
# 해당하는 파일의 들여쓰기를 예쁘게(-_-) 만든다.
def decorate(filename):
    f = file(filename, "r")
 
    originals      = f.readlines()
    seperator      = "="
    comment        = "//"
    lhs_max_length = 0
 
    for line in originals:
        pos = string.find(line, seperator)
        if pos != -1:
            lhs = line[:pos].strip()
            rhs = line[pos+1:].strip()
            lhs_max_length = max(lhs_max_length, len(lhs))
 
    f.close()
    f = file(filename, "w")
 
    for line in originals:
        pos = string.find(line, seperator)
        text = line
        if pos != -1:
            lhs = line[:pos].strip()
            rhs = line[pos+1:].strip()
            needed_space = lhs_max_length - len(lhs);
            for i in range(0, needed_space):
            	lhs = lhs + " ";
            text = lhs + " " + seperator + " " + rhs + "\n"
 
        f.write(text)
 
    f.close()
 
input_files = [
    "D:\\Project\\Binary\\Shared\\_NetLib\\TypesSkill.h",
    "D:\\Project\\Binary\\Shared\\_NetLib\\TypesCreature.h"
]
 
generate(input_files, "test.lua")
decorate("test.lua")

루아 버전

--[[
@file extract_enums.lua
@author excel96
@brief C++ 소스에서 열거값 데이터를 LUA 소스로 뽑아내기 위한 스크립트
--]]
 
-- 문자열에서 앞뒷쪽의 빈 공간을 날린다.
function strip(text)
    return string.gsub(text, "^[ \t\r\n]+|[ \t\r\n]+$", "")
end
 
-- 문자열에서 C++ identifier를 뽑아낸다.
-- "this_is_identifier = 0;" --> "this_is_identifier"
function extract_id(text)
    if text == nil then return "" end
    local b, e = string.find(text, "[a-zA-Z_][a-zA-Z0-9_]*")
    if b ~= nil then return strip(string.sub(text, b, e)) end
    return ""
end
 
-- 문자열에서 코멘트 문자열을 뽑아낸다. 
-- 원래 라인 주석은 "//" 뒤쪽의 모든 문자열이지만,
-- 현재 소스의 특수성(doxygen)을 고려하여 "///<"에 대한 처리도 집어넣었다.
-- "// this is comment" --> "this is comment"
-- "///< this is comment" --> "this is comment"
function extract_comment(text)
    local _, _, m1, m2 = string.find(text, "(//[/<]*)(.*)")
    if m2 ~= nil then return strip(m2) end
    return ""
end
 
-- 주어진 N개의 파일들에서 열거값을 뽑아내어,
-- 하나의 출력 파일에다 그 값들을 기록한다.
function generate(input_filenames, output_filename)
    -- 출력 파일을 연다.
    local output = assert(io.open(output_filename, "w"))
 
    -- 입력 파일 목록 모두를 열어서 열거값을 출력 파일에다 기록한다.
    for _, filename in pairs(input_filenames) do
 
        local bEnum = false -- 현재 열거값 내부를 처리 중인가?
        local count = 0 -- 열거값 처리 중이라면 현재 열거값의 실제 값은?
 
        for line in io.lines(filename) do
            -- 열거값 처리 중이라면...
            if bEnum then
                -- 열거값 구문의 끝에 도달했는지 체크한다.
                if string.find(line, "};") ~= nil then
                    output:write("\n")
                    bEnum = false
                -- 열거값 구문의 끝이 아니라면, 열거값과 그 값을 기록한다.
                else
                    local id = extract_id(line)
                    if string.len(id) > 0 then
                        local text = id .. " = " .. tostring(count)
 
                        -- 열거값 뒤쪽에 붙은 주석을 처리한다.
                        local comment = extract_comment(line)
                        if string.len(comment) > 0 then
                            text = text .. " -- " .. comment
                        end
 
                        output:write(text .. "\n")
                        count = count + 1
                    end
                end
            -- 열거값 구문이 새로 시작되는지 체크한다.
            elseif string.find(line, "^enum") ~= nil then
                bEnum = true
                count = 0
            end
        end
    end
 
    output:close()
end
 
-- 해당하는 파일의 들여쓰기를 예쁘게(-_-) 만든다.
function decorate(filename)
    local originals      = {}
    local seperator      = "="
    local comment        = "//"
    local lhs_max_length = 0
 
    for line in io.lines(filename) do
        local b, e = string.find(line, seperator)
        if b ~= nil then
            local lhs = strip(string.sub(line, 1, b-1))
            local rhs = strip(string.sub(line, b+1, string.len(line)))
            lhs_max_length = math.max(lhs_max_length, string.len(lhs))
        end
        table.insert(originals, line)
    end
 
    f = assert(io.open(filename, "w"))
 
    for _, line in pairs(originals) do
        local text = line
 
        local b, e = string.find(line, seperator)
        if b ~= nil then
            local lhs = strip(string.sub(line, 1, b-1))
            local rhs = strip(string.sub(line, b+1, string.len(line)))
            local needed_space = lhs_max_length - string.len(lhs)
            for i=1,needed_space do
            	lhs = lhs .. " "
            end
            text = lhs .. seperator .. rhs .. "\n"
        else
            text = text .. "\n"
        end
 
        f:write(text)
    end
 
    f:close()
end
 
input_files = {
    "D:\\Project\\Binary\\Shared\\_NetLib\\TypesSkill.h",
    "D:\\Project\\Binary\\Shared\\_NetLib\\TypesCreature.h"
}
 
generate(input_files, "test.lua")
decorate("test.lua")

루비 버전

# \file extract_enums.py
# \brief C++ 소스에서 열거값 데이터를 LUA 소스로 뽑아내기 위한 스크립트
 
# 문자열에서 C++ identifier를 뽑아낸다.
# "this_is_identifier = 0;" --> "this_is_identifier"
def extract_id(text)
    match = text.scan(/[a-zA-Z_][a-zA-Z0-9_]*/)
    return match.length > 0 ? match[0] : nil
end
 
# 문자열에서 C++ identifier를 뽑아낸다.
# "this_is_identifier = 0;" --> "this_is_identifier"
def extract_id_ex(text)
    match = text.scan(/([a-zA-Z_][a-zA-Z0-9_]*)([ \t]*=[ \t]*)/)
    return match.length > 0 ? match[0][0] : nil
end
 
# 문자열에서 " = value" 구문을 찾은 다음, "value" 부분을 뽑아낸다.
# "a = 32" --> "32"
def extract_value(text)
    match = text.scan(/(=)([ \t\r\n]*)([\"a-zA-Z0-9_.]*)/)
    return match.length > 0 ? match[0][2] : nil
end
 
# 문자열이 숫자로만 이루어졌는지의 여부를 반환한다.
def is_digit(text)
    return text.scan(/^[0-9]+$/).length > 0
end
 
# 문자열에서 코멘트 문자열을 뽑아낸다. 
# 원래 라인 주석은 "//" 뒤쪽의 모든 문자열이지만,
# 현재 소스의 특수성(doxygen)을 고려하여 "///<"에 대한 처리도 집어넣었다.
# "// this is comment" --> "this is comment"
# "///> this is comment" --> "this is comment"
def extract_comment(text)
    match = text.scan(/(\/\/[\/<]*)(.*)/)
    return match.length > 0 ? match[0][1].strip : nil
end
 
# 해당하는 파일의 들여쓰기를 예쁘게(-_-) 만든다.
def decorate(filename)
    originals      = IO.readlines(filename)
    seperator      = "="
    comment        = "//"
    lhs_max_length = 0
 
    for line in originals
        pos = line.index(seperator)
        if pos != nil
            lhs = line[0, pos].to_s.strip # nil인 경우가 있어서 to_s 처리
            rhs = line[pos+1..-1].to_s.strip # nil인 경우가 있어서 to_s 처리
            lhs_max_length = [lhs_max_length, lhs.length].max
        end
    end
 
    f = File.open(filename, "w")
 
    for line in originals
        text = line
        pos = line.index(seperator)
        if pos != nil
            lhs = line[0, pos].to_s.strip # nil인 경우가 있어서 to_s 처리
            rhs = line[pos+1..-1].to_s.strip # nil인 경우가 있어서 to_s 처리
            lhs = lhs + (" " * (lhs_max_length - lhs.length))
            text = lhs.to_s + " " + seperator + " " + rhs.to_s + "\n"
        end
 
        f.write(text)
    end
 
    f.close
end
 
# 주어진 N개의 파일들에서 열거값을 뽑아내어,
# 하나의 출력 파일에다 그 값들을 기록한다.
def generate(input_filenames, output_filename)
    # 출력 파일을 연다.
    output = File.open(output_filename, "w")
 
    # 헤더를 적어준다.
    output.write("--[[\n")
    output.write("\\file " + output_filename + "\n")
    output.write("\\author excel96\n")
    output.write("\\brief 자동으로 생성된 파일입니다.\n")
    output.write("잘못된 것이 있다면 수동으로 고치지 말고 스크립트를 손봐야 합니다.\n")
    output.write("--]]\n\n")
 
    # 입력 파일 목록 모두를 열어서 열거값을 출력 파일에다 기록한다.
    for filename in input_filenames
        bEnum = false # 현재 열거값 내부를 처리 중인가?
        count = 0 # 열거값 처리 중이라면 현재 열거값의 실제 값은?
 
        # 원본 파일에 대한 정보를 기록한다.
        header = "-- EXTRACTED FROM " + filename
        output.write("-" * (header.length + 1) + "\n")
        output.write(header + "\n")
        output.write("-" * (header.length + 1) + "\n\n")
 
        for line in IO.readlines(filename)
            # 열거값 처리 중이라면...
            if bEnum
                # 열거값 구문의 끝에 도달했는지 체크한다.
                if line.scan(/^\s*\}\s*;/).length > 0
                    output.write("\n")
                    bEnum = false
                # 열거값 구문의 끝이 아니라면, 열거값과 그 값을 기록한다.
                else
                    if id = extract_id(line)
                        text = id
 
                        # 값 문자열이 있는지 확인한다.
                        if value = extract_value(line)
                            if is_digit(value)
                                text = text + " = " + value
                                count = value.to_i
                            else
                                text = text + " = " + value
                            end
                        else
                            text = text + " = " + count.to_s
                        end
 
                        # 뒤쪽에 붙은 주석을 처리한다.
                        if comment = extract_comment(line)
                            text = text + " -- " + comment 
                        end
 
                        output.write(text + "\n")
                        count = count + 1
                    end
                end
            # 열거값 구문이 새로 시작되는지 체크한다.
            elsif line.scan(/(^[ \t]*enum)([\t ]+[a-zA-Z_][a-zA-Z0-9_]*)*/).length > 0
                bEnum = 1
                count = 0
            # 상수 구문인지를 체크한다.
            elsif line.scan(/(static|)\s*const\s*(static|)\s*(unsigned|)\s*(int|float)\s*[A-Za-z0-9_]+\s*=\s*[\"A-Za-z0-9.]+;/).length > 0
                id = extract_id_ex(line)
                value = extract_value(line)
                text = id + " = " + value
 
                # 뒤쪽에 붙은 주석을 처리한다.
                if comment = extract_comment(line)
                    text = text + " -- " + comment 
                end
 
                output.write(text + "\n\n")
            end
        end
    end
 
    output.close
 
    # 들여쓰기를 손본다.
    decorate(output_filename)
end
 
files1 = [
    "D:\\Project\\XM\\Server\\ZTestAI\\Globals.h",
    "D:\\Project\\XM\\Server\\ZTestAI\\NPC.h"
]
 
generate(files1, "Constant.lua")

kb/cppidentifierextraction.txt · 마지막으로 수정됨: 2014/11/08 14:34 (바깥 편집)