|
| 1 | +__precompile__(true) |
| 2 | +""" |
| 3 | +API Tools package |
| 4 | +
|
| 5 | +Copyright 2018 Gandalf Software, Inc., Scott P. Jones |
| 6 | +Licensed under MIT License, see LICENSE.md |
| 7 | +
|
| 8 | +(@def macro "stolen" from DiffEqBase.jl/src/util.jl :-) ) |
| 9 | +""" |
| 10 | +module APITools |
| 11 | +export @api, @def |
| 12 | + |
| 13 | +macro def(name, definition) |
| 14 | + quote |
| 15 | + macro $(esc(name))() |
| 16 | + esc($(Expr(:quote, definition))) |
| 17 | + end |
| 18 | + end |
| 19 | +end |
| 20 | + |
| 21 | +struct TMP_API |
| 22 | + base::Vector{Symbol} |
| 23 | + public::Vector{Symbol} |
| 24 | + develop::Vector{Symbol} |
| 25 | + define_public::Vector{Symbol} |
| 26 | + define_develop::Vector{Symbol} |
| 27 | + TMP_API() = new(Symbol[], Symbol[], Symbol[], Symbol[], Symbol[]) |
| 28 | +end |
| 29 | + |
| 30 | +const SymList = Tuple{Vararg{Symbol}} |
| 31 | + |
| 32 | +struct API |
| 33 | + mod::Module |
| 34 | + base::SymList |
| 35 | + public::SymList |
| 36 | + develop::SymList |
| 37 | + define_public::SymList |
| 38 | + define_develop::SymList |
| 39 | + |
| 40 | + API(mod, api::TMP_API) = |
| 41 | + new(mod, |
| 42 | + SymList(api.base), SymList(api.public), SymList(api.develop), |
| 43 | + SymList(api.define_public), SymList(api.define_develop)) |
| 44 | +end |
| 45 | + |
| 46 | +const APIList = Tuple{Vararg{API}} |
| 47 | + |
| 48 | +""" |
| 49 | +@api <cmd> [<symbols>...] |
| 50 | +
|
| 51 | + * @api init # set up module/package for adding names |
| 52 | + * @api freeze # use at end of module, to "freeze" API |
| 53 | +
|
| 54 | + * @api use <modules>... # use for normal use |
| 55 | + * @api test <modules>... # using api and dev, for testing purposes |
| 56 | + * @api extend <modules>... # for development, imports api & dev, use api & dev definitions |
| 57 | + * @api export <modules>... # export api symbols |
| 58 | +
|
| 59 | + * @api base <names...> # Add functions from Base that are part of the API |
| 60 | + * @api public <names...> # Add functions that are part of the public API |
| 61 | + * @api develop <names...> # Add functions that are part of the development API |
| 62 | + * @api define_public <names...> # Add other symbols that are part of the public API (structs, consts) |
| 63 | + * @api define_develop <names...> # Add other symbols that are part of the development API |
| 64 | +""" |
| 65 | +macro api(cmd::Symbol) |
| 66 | + if cmd == :init |
| 67 | + quote |
| 68 | + global __tmp_api__ = APITools.TMP_API() |
| 69 | + global __tmp_chain__ = Vector{APITools.API}[] |
| 70 | + end |
| 71 | + elseif cmd == :freeze |
| 72 | + @static if VERSION < v"0.7.0-DEV" |
| 73 | + esc(quote |
| 74 | + const __chain__ = APITools.APIList(__tmp_chain__) |
| 75 | + const __api__ = APITools.API(current_module(), __tmp_api__) |
| 76 | + __tmp_chain__ = _tmp_api__ = nothing |
| 77 | + end) |
| 78 | + else |
| 79 | + esc(quote |
| 80 | + const __chain__ = APITools.APIList(__tmp_chain__) |
| 81 | + const __api__ = APITools.API(@__MODULE__, __tmp_api__) |
| 82 | + __tmp_chain__ = _tmp_api__ = nothing |
| 83 | + end) |
| 84 | + end |
| 85 | + else |
| 86 | + error("@api unrecognized command: $cmd") |
| 87 | + end |
| 88 | +end |
| 89 | + |
| 90 | +const _cmdadd = (:define_public, :define_develop, :public, :develop, :base) |
| 91 | +const _cmduse = (:use, :test, :extend, :export) |
| 92 | + |
| 93 | +@static VERSION < v"0.7.0-DEV" && (const _ff = findfirst) |
| 94 | +@static VERSION < v"0.7.0-DEV" || |
| 95 | + (_ff(lst, val) = coalesce(findfirst(isequal(val), lst), 0)) |
| 96 | + |
| 97 | +function _add_symbols(grp, exprs) |
| 98 | + symbols = Symbol[] |
| 99 | + for ex in exprs |
| 100 | + if isa(ex, Expr) && ex.head == :tuple |
| 101 | + append!(symbols, ex.args) |
| 102 | + elseif isa(ex, Symbol) |
| 103 | + push!(symbols, ex) |
| 104 | + else |
| 105 | + error("@api $grp: syntax error $ex") |
| 106 | + end |
| 107 | + end |
| 108 | + esc(:( append!(__tmp_api__.$grp, $symbols) )) |
| 109 | +end |
| 110 | + |
| 111 | +function _make_modules(exprs) |
| 112 | + uselst = Expr[] |
| 113 | + modlst = Symbol[] |
| 114 | + for ex in exprs |
| 115 | + if isa(ex, Expr) && ex.head == :tuple |
| 116 | + append!(modlst, ex.args) |
| 117 | + for e in ex.args ; push!(uselst, :(using $sym)) ; end |
| 118 | + elseif isa(ex, Symbol) |
| 119 | + push!(modlst, ex) |
| 120 | + push!(uselst, :(using $ex)) |
| 121 | + else |
| 122 | + error("@api $cmd: syntax error $ex") |
| 123 | + end |
| 124 | + end |
| 125 | + uselst, modlst |
| 126 | +end |
| 127 | + |
| 128 | +macro api(cmd::Symbol, exprs...) |
| 129 | + ind = _ff(_cmdadd, cmd) |
| 130 | + ind == 0 || return _add_symbols(cmd, exprs) |
| 131 | + |
| 132 | + ind = _ff(_cmduse, cmd) |
| 133 | + ind == 0 && error("@api unrecognized command: $cmd") |
| 134 | + |
| 135 | + lst, modules = _make_modules(exprs) |
| 136 | + |
| 137 | + if ind == 1 # use |
| 138 | + grplst = (:public, :define_public) |
| 139 | + elseif ind == 2 # test |
| 140 | + grplst = (:public, :define_public, :develop, :define_develop) |
| 141 | + elseif ind == 3 # extend |
| 142 | + grplst = (:define_public, :define_develop) |
| 143 | + for mod in modules, grp in (:base, :public, :develop) |
| 144 | + push!(lst, _make_exprs(:import, mod, grp)) |
| 145 | + end |
| 146 | + else # export |
| 147 | + grplst = () |
| 148 | + for mod in modules, grp in (:public, :define_public) |
| 149 | + push!(lst, :(eval(Expr( :export, $mod.__api__.$grp... )))) |
| 150 | + end |
| 151 | + end |
| 152 | + for mod in modules, grp in grplst |
| 153 | + push!(lst, _make_exprs(:using, mod, grp)) |
| 154 | + end |
| 155 | + esc(Expr(:toplevel, lst...)) |
| 156 | +end |
| 157 | + |
| 158 | +# We need Expr(:toplevel, (Expr($cmd, $mod, $sym) for sym in $mod.__api__.$grp)...) |
| 159 | + |
| 160 | +function _make_list(cmd, mod, lst) |
| 161 | + isempty(lst) && return nothing |
| 162 | + @static if VERSION < v"0.7.0-DEV" |
| 163 | + length(lst) > 1 ? |
| 164 | + Expr(:toplevel, [Expr(cmd, mod, nam) for nam in lst]...) : Expr(cmd, mod, lst[1]) |
| 165 | + else |
| 166 | + Expr(cmd, Expr(:(:), Expr(:., mod), [Expr(:., nam) for nam in lst]...)) |
| 167 | + end |
| 168 | +end |
| 169 | + |
| 170 | +function _make_exprs(cmd, mod, grp) |
| 171 | + from = QuoteNode(grp == :base ? :Base : mod) |
| 172 | + :(eval($(Meta.parse("APITools._make_list($(QuoteNode(cmd)), $from, $mod.__api__.$grp)")))) |
| 173 | +end |
| 174 | + |
| 175 | +end # module APITools |
0 commit comments