summaryrefslogtreecommitdiff
path: root/xmake/scripts/base/preprocessor.lua
blob: a8fd9fa39eb0a882c89d34c59413bb158f6a620c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
--!The Automatic Cross-platform Build Tool
-- 
-- XMake is free software; you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation; either version 2.1 of the License, or
-- (at your option) any later version.
-- 
-- XMake is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Lesser General Public License for more details.
-- 
-- You should have received a copy of the GNU Lesser General Public License
-- along with XMake; 
-- If not, see <a href="http://www.gnu.org/licenses/"> http://www.gnu.org/licenses/</a>
-- 
-- Copyright (C) 2009 - 2015, ruki All rights reserved.
--
-- @author      ruki
-- @file        preprocessor.lua
--

-- define module: preprocessor
local preprocessor = preprocessor or {}

-- load modules
local utils = require("base/utils")

-- filter value
function preprocessor._filter(env, value, filter)

    -- the value is string?
    if type(value) == "string" then

        -- init
        local _v = nil
        local _n = 0

        -- replace it for filter
        if filter then
            -- replace $(variable)
            _v, _n = value:gsub("%$%((.*)%)", function (v) return filter(env, v) end)
        end

        -- replace it for script
        if _n == 0 then
            _v, _n = value:gsub("%[(.*)%]",     function (v) 

                                                    -- load the script
                                                    local script = assert(loadstring("return " .. v))

                                                    -- bind the envirnoment
                                                    setfenv(script, env)

                                                    -- done it
                                                    local ok, result = pcall(script)
                                                    if not ok then return end

                                                    -- ok?
                                                    return result or ""
                                                end)
        end

        -- update value
        value = _v
    end

    -- ok
    return value
end

-- register configures
function preprocessor._register(env, names, filter)

    -- check
    assert(env and names and type(names) == "table")
    assert(not filter or type(filter) == "function")

    -- register all configures
    for _, name in ipairs(names) do

        -- register the configure 
        env[name] = env[name] or function(...)

            -- check
            local _current = env._current
            assert(_current)

            -- init the configure entry
            _current[name] = _current[name] or {}

            -- get arguments
            local arg = arg or {...}
            if table.getn(arg) == 0 then
                -- no argument
                _current[name] = nil
            else
                -- save all arguments
                for _, v in ipairs(arg) do
                    v = preprocessor._filter(env, v, filter)
                    if v and type(v) == "string" and #v ~= 0 then
                        table.insert(_current[name], v)
                    end
                end
            end
        end
    end
end

-- init configures
function preprocessor._init(root, configures, scopes, filter, import)

    -- check
    assert(root and configures)

    -- enter new environment 
    local newenv = {}
    local oldenv = getfenv()
    setmetatable(newenv, {  __index =   function(tbl, key)
                                            -- private configure entry? we can get it directly and need not register it
                                            if key and type(key) == "string" and key:startswith("__") then

                                                return function(...)

                                                    -- check
                                                    local _current = newenv._current
                                                    assert(_current)

                                                    -- init the configure entry
                                                    _current[key] = _current[key] or {}

                                                    -- get arguments
                                                    local arg = arg or {...}
                                                    if table.getn(arg) == 0 then
                                                        -- no argument
                                                        _current[key] = nil
                                                    elseif table.getn(arg) == 1 then
                                                        -- save only one argument
                                                        _current[key] = arg[1]
                                                    else
                                                        -- save all arguments
                                                        for i, v in ipairs(arg) do
                                                            _current[key][i] = v
                                                        end
                                                    end
                                                end
                                            end

                                            -- get it from the global table
                                            return rawget(_G, key)
                                        end})  
    setfenv(1, newenv)

    -- register all configures
    preprocessor._register(newenv, configures, filter)

    -- configure import
    if import then
        newenv["import"] = import
    end

    -- configure scope end
    newenv["_end"] = function ()

        -- check
        assert(_current)

        -- leave the current scope
        _current = _current._PARENT
    end

    -- configure scopes
    if scopes then
        for _, scope_name in ipairs(scopes) do
                
            newenv[scope_name] = function (...)

                -- check
                assert(_current)

                -- init config name
                local config_name = "_" .. scope_name:upper()

                -- init scope config
                _current[config_name] = _current[config_name] or {}
                local scope_config = _current[config_name]

                -- init scope
                local scope = {}

                -- configure all 
                local arg = arg or {...}
                for _, name in ipairs(arg) do

                    -- check
                    if scope_config[name] then
                        -- error
                        utils.error("the %s: %s has been defined repeatly!", config_name, name)
                        assert(false) 
                    end

                    -- init the scope
                    scope_config[name] = scope

                end

                -- enter scope
                local parent = _current
                _current = scope
                _current._PARENT = parent
            end
        end
    end

    -- the root configure 
    newenv[root] = function ()

        -- init the root scope, must be only one configs
        if not newenv._CONFIGS then
            newenv._CONFIGS = {}
        else
            -- error
            utils.error("exists double %s!", root)
            return
        end

        -- init the current scope
        _current = newenv._CONFIGS
        _current._PARENT = nil
    end

    -- enter old environment 
    setfenv(1, oldenv)
    return newenv
end
 
--!load the configure file
--
-- supports:
--     xmake.xproj
--     xmake.xconf
--
-- @code
-- local configs, errors = preprocessor.loadfile("xmake.xconf", "config", {"plat", "host", "arch", ...}, {"target"})
-- local configs, errors = preprocessor.loadfile("xmake.xproj", "project", {"links", "files", "ldflags", ...}, {"target", "platforms"}, filter, import)
-- @endcode
function preprocessor.loadfile(path, root, configures, scopes, filter, import)

    -- check
    assert(path and root and configures)

    -- load and execute the configure file
    local script = preprocessor.loadx(path)
    if script then

        -- init a new envirnoment
        local newenv = preprocessor._init(root, configures, scopes, filter, import)
        assert(newenv)

        -- bind this envirnoment
        setfenv(script, newenv)

        -- execute it
        local ok, err = pcall(script)
        if not ok then
            -- error
            return nil, err
        end

        -- ok?
        return newenv
    else
        -- error
        return nil, string.format("load %s failed!", path)
    end
end

-- return module: preprocessor
return preprocessor