diff --git a/base/Enums.jl b/base/Enums.jl index 5c4eadf48624f..1de0114209a36 100644 --- a/base/Enums.jl +++ b/base/Enums.jl @@ -2,6 +2,8 @@ module Enums +using Base.Meta + export Enum, @enum abstract Enum @@ -24,41 +26,26 @@ end @noinline enum_argument_error(typename, x) = throw(ArgumentError(string("invalid value for Enum $(typename): $x"))) macro enum(T,syms...) - if isempty(syms) - throw(ArgumentError("no arguments given for Enum $T")) - end - if isa(T,Symbol) - typename = T - else - throw(ArgumentError("invalid type expression for enum $T")) - end - vals = Array(Tuple{Symbol,Integer},0) + isempty(syms) && throw(ArgumentError("no arguments given for Enum $T")) + isa(T,Symbol) ? (typename = T) : throw(ArgumentError("invalid type expression for enum $T")) + vals = Tuple{Symbol,Integer}[] lo = hi = 0 i = -1 enumT = typeof(i) hasexpr = false for s in syms - if isa(s,Symbol) - if i == typemax(typeof(i)) - i = widen(i) + one(i) - else - i += one(i) - end - elseif isa(s,Expr) && - (s.head == :(=) || s.head == :kw) && - length(s.args) == 2 && isa(s.args[1],Symbol) - i = eval(s.args[2]) # allow exprs, e.g. uint128"1" - s = s.args[1] - hasexpr = true - else - throw(ArgumentError(string("invalid argument for Enum ", typename, ": ", s))) + isexpr(s, :kw) && (s.head = :(=)) + @match s begin + s_Symbol -> i == typemax(typeof(i)) ? + (i = widen(i) + one(i)) : + (i += one(i)) + (ss_Symbol = ii_) -> (s, i, hasexpr) = (ss, eval(ii), true) + _ -> throw(ArgumentError(string("invalid argument for Enum ", typename, ": ", s))) end - if !isa(i, Integer) + isa(i, Integer) || throw(ArgumentError("invalid value for Enum $typename, $s=$i; values must be integers")) - end - if !Base.isidentifier(s) + Base.isidentifier(s) || throw(ArgumentError("invalid name for Enum $typename; \"$s\" is not a valid identifier.")) - end push!(vals, (s,i)) I = typeof(i) if length(vals) == 1 diff --git a/base/c.jl b/base/c.jl index 6761dcadd14c7..1e8364c7f0f45 100644 --- a/base/c.jl +++ b/base/c.jl @@ -105,11 +105,7 @@ macro ccallable(def) if sig.head === :call name = sig.args[1] at = map(sig.args[2:end]) do a - if isa(a,Expr) && a.head === :(::) - a.args[2] - else - :Any - end + isa(a,Expr) && a.head === :(::) ? a.args[2] : :Any end return quote $(esc(def)) diff --git a/base/deprecated.jl b/base/deprecated.jl index 73b622bf95865..a89d60dc79c44 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -18,6 +18,8 @@ # the name of the function, which is used to ensure that the deprecation warning # is only printed the first time for each call place. +using Base.Meta + macro deprecate(old,new) meta = Expr(:meta, :noinline) if isa(old,Symbol) @@ -34,13 +36,8 @@ macro deprecate(old,new) elseif isa(old,Expr) && old.head == :call oldcall = sprint(io->show_unquoted(io,old)) newcall = sprint(io->show_unquoted(io,new)) - oldsym = if isa(old.args[1],Symbol) - old.args[1] - elseif isa(old.args[1],Expr) && old.args[1].head == :curly - old.args[1].args[1] - else - error("invalid usage of @deprecate") - end + oldsym = uncurly(old.args[1]) + isa(oldsym, Symbol) || error("invalid usage of @deprecate") oldname = Expr(:quote, oldsym) Expr(:toplevel, Expr(:export,esc(oldsym)), diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index 3b51fad3ca7cc..17bc6d6f05e6f 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -2,6 +2,7 @@ module Docs +using Base.Meta import Base.Markdown: @doc_str, MD export doc @@ -220,6 +221,13 @@ end const keywords = Dict{Symbol,Any}() +# Bindings + +function doc(b::Meta.Binding) + d = invoke(doc, (Any,), b) + d != nothing ? d : doc(b[]) +end + # Usage macros isexpr(x::Expr) = true @@ -227,20 +235,6 @@ isexpr(x) = false isexpr(x::Expr, ts...) = x.head in ts isexpr(x, ts...) = any(T->isa(T, Type) && isa(x, T), ts) -function unblock(ex) - isexpr(ex, :block) || return ex - exs = filter(ex->!isexpr(ex, :line), ex.args) - length(exs) == 1 || return ex - # Recursive unblock'ing for macro expansion - return unblock(exs[1]) -end - -uncurly(ex) = isexpr(ex, :curly) ? ex.args[1] : ex - -namify(ex::Expr) = isexpr(ex, :.) ? ex : namify(ex.args[1]) -namify(ex::QuoteNode) = ex.value -namify(sy::Symbol) = sy - function mdify(ex) if isexpr(ex, AbstractString, :string) :(Markdown.parse($(esc(ex)))) @@ -285,41 +279,59 @@ function objdoc(meta, def) end end -fexpr(ex) = isexpr(ex, :function, :(=)) && isexpr(ex.args[1], :call) +function vardoc(meta, def, name) + quote + @init + f = $(esc(def)) + # @var isn't found – bug? + doc!(Meta.@var($(esc(name))), $(mdify(meta))) + f + end +end function docm(meta, def) - # Quote, Unblock and Macroexpand - # * Always do macro expansion unless it's a quote (for consistency) - # * Unblock before checking for Expr(:quote) to support `->` syntax - # * Unblock after macro expansion to recognize structures of - # the generated AST - def′ = unblock(def) - if !isexpr(def′, :quote) - def = macroexpand(def) - def′ = unblock(def) - elseif length(def′.args) == 1 && isexpr(def′.args[1], :macrocall) - # Special case for documenting macros after definition with - # `@doc "" :@macro` or - # `@doc "" :(str_macro"")` syntax. - # - # Allow more general macrocall for now unless it causes confusion. - return objdoc(meta, namify(def′.args[1])) - end - isexpr(def′, :macro) && return namedoc(meta, def, symbol("@", namify(def′))) - isexpr(def′, :type) && return typedoc(meta, def, namify(def′.args[2])) - isexpr(def′, :bitstype) && return namedoc(meta, def, def′.args[2]) - isexpr(def′, :abstract) && return namedoc(meta, def, namify(def′)) - isexpr(def′, :module) && return namedoc(meta, def, def′.args[2]) - fexpr(def′) && return funcdoc(meta, def) - return objdoc(meta, def) + @match def begin + :(@m_) -> return vardoc(meta, nothing, m) + m_"" -> return vardoc(meta, nothing, m) + (@var x_) -> vardoc(meta, def, x) + end + def = macroexpand(def) + @match def begin + (f_(__) = _) -> funcdoc(meta, def) + function f_(__) _ end -> funcdoc(meta, def) + function f_ end -> objdoc(meta, def) + macro m_(__) _ end -> vardoc(meta, def, symbol("@", m)) + + type T_ _ end -> typedoc(meta, def, namify(T)) + immutable T_ _ end -> typedoc(meta, def, namify(T)) + (abstract T_) -> namedoc(meta, def, namify(T)) + (bitstype _ T_) -> namedoc(meta, def, namify(T)) + (typealias T_ _) -> objdoc(meta, def) + + module M_ _ end -> namedoc(meta, def, M) + + (x_ = _) -> vardoc(meta, def, namify(x)) + (const x_ = _) -> vardoc(meta, def, namify(x)) + + _Expr -> error("Unsupported @doc syntax $def") + _ -> objdoc(meta, def) + end end function docm(ex) isa(ex,Symbol) && haskey(keywords, ex) && return keywords[ex] - isexpr(ex, :->) && return docm(ex.args...) - isexpr(ex, :call) && return :(doc($(esc(ex.args[1])), @which $(esc(ex)))) - isexpr(ex, :macrocall) && (ex = namify(ex)) - :(doc($(esc(ex)))) + @match ex begin + (meta_ -> def_) -> docm(meta, def) + f_(__) -> :(doc($(esc(f))), @which $(esc(ex))) + + (@m_) -> :(doc(Meta.@var($(esc(ex))))) + (_.@m_) -> :(doc(Meta.@var($(esc(ex))))) + (_._) -> :(doc(Meta.@var($(esc(ex))))) + (@var x_) -> :(doc(Meta.@var($(esc(x))))) + + _Symbol -> :(doc(Meta.@var($(esc(ex))))) + _ -> :(doc($(esc(ex)))) + end end # Not actually used; bootstrap version in bootstrap.jl @@ -524,7 +536,7 @@ macro repl(ex) haskey(keywords, $(Expr(:quote, ex)))) repl_corrections($(string(ex))) else - if $(isfield(ex) ? :(isa($(esc(ex.args[1])), DataType)) : false) + if $(isfield(ex) && :(isa($(esc(ex.args[1])), DataType))) $(isfield(ex) ? :(fielddoc($(esc(ex.args[1])), $(ex.args[2]))) : nothing) else # Backwards-compatible with the previous help system, for now diff --git a/base/meta.jl b/base/meta.jl index 6c40d1028c6bd..ab0fed687f9a4 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -7,7 +7,17 @@ module Meta export quot, isexpr, - show_sexpr + show_sexpr, + isline, + rmlines, + unblock, + namify, + uncurly, + isdef, + subs, + @expand, + @match, + @var quot(ex) = Expr(:quote, ex) @@ -18,6 +28,33 @@ isexpr(ex, head) = false isexpr(ex, head, n::Int) = isexpr(ex, head) && length(ex.args) == n +isline(ex) = isexpr(ex, :line) || isa(ex, LineNumberNode) + +rmlines(xs) = filter(x->!isline(x), xs) +rmlines(x::Expr) = Expr(x.head, rmlines(x.args)...) + +function unblock(ex) + isexpr(ex, :block) || return ex + exs = filter(ex->!isline(ex), ex.args) + length(exs) == 1 || return ex + return unblock(exs[1]) +end + +namify(ex::Expr) = isexpr(ex, :.) ? ex : namify(ex.args[1]) +namify(ex::QuoteNode) = ex.value +namify(sy::Symbol) = sy + +uncurly(ex) = isexpr(ex, :curly) ? ex.args[1] : ex + +isdef(ex) = isexpr(ex, :function) || (isexpr(ex, :(=)) && isexpr(ex.args[1], :call)) + +subs(ex::Expr, s, s′) = ex == s ? s′ : Expr(ex.head, map(ex -> subs(ex, s, s′), ex.args)...) + +subs(ex, s, s′) = ex == s ? s′ : ex + +macro expand(ex) + :(macroexpand($(Expr(:quote, ex)))) +end # ---- show_sexpr: print an AST as an S-expression ---- @@ -46,4 +83,215 @@ function show_sexpr(io::IO, ex::Expr, indent::Int) end end +# Expression Matching + +type MatchError + pat + ex +end + +nomatch(pat, ex) = throw(MatchError(pat, ex)) + +isbinding(s) = false +isbinding(s::Symbol) = Base.ismatch(r"[^_]_(_str)?$", string(s)) + +bname(s::Symbol) = symbol(Base.match(r"^@?(\w*?)_+", string(s)).captures[1]) + +match_inner(pat, ex, env) = (pat == ex ? env : nomatch(pat, ex)) + +isslurp(s) = false +isslurp(s::Symbol) = s == :__ || Base.ismatch(r"[^_]__$", string(s)) + +function slurprange(pat) + slurps = length(filter(isslurp, pat)) + slurps == 0 && return 0,0 + slurps > 1 && error("Pattern may only contain one slurp.") + + left, right = 1, 1 + while !isslurp(pat[left]) left += 1 end + while !isslurp(pat[end+1-right]) right += 1 end + return left, right +end + +inrange(i, range, len) = + range ≠ (0,0) && i ≥ range[1] && i ≤ len+1-range[2] + +function match_inner(pat::Expr, ex::Expr, env) + match(pat.head, ex.head, env) + pat, ex = rmlines(pat), rmlines(ex) + sr = slurprange(pat.args) + slurp = Any[] + i = 1 + for p in pat.args + i > length(ex.args) && + (isslurp(p) ? (env[bname(p)] = slurp) : nomatch(pat, ex)) + + while inrange(i, sr, length(ex.args)) + push!(slurp, ex.args[i]) + i += 1 + end + + if isslurp(p) + p ≠ :__ && (nv[bname(p)] = slurp) + else + match(p, ex.args[i], env) + i += 1 + end + end + i == length(ex.args)+1 || nomatch(pat, ex) + return env +end + +blockunify(a, b) = + isexpr(a, :block) && !isexpr(b, :block) ? (a, Expr(:block, b)) : + !isexpr(a, :block) && isexpr(b, :block) ? (Expr(:block, a), b) : + (a, b) + +function normalise(ex) + ex = unblock(ex) + isa(ex, QuoteNode) && (ex = Expr(:quote, ex.value)) + return ex +end + +function match(pat, ex, env) + pat, ex = normalise(pat), normalise(ex) + pat == :_ && return env + isbinding(pat) && return (env[bname(pat)] = ex; env) + pat, ex = blockunify(pat, ex) + return match_inner(pat, ex, env) +end + +match(pat, ex) = match(pat, ex, Dict()) + +function trymatch(pat, ex) + try match(pat, ex) + catch e isa(e, MatchError) ? nothing : rethrow() + end +end + +# Typed bindings + +immutable TypeBind + name::Symbol + ts::Set{Any} +end + +istb(s) = false +istb(s::Symbol) = !(endswith(string(s), "_") || endswith(string(s), "_str")) && contains(string(s), "_") + +tbname(s::Symbol) = symbol(split(string(s), "_")[1]) +tbname(s::TypeBind) = s.name + +totype(s::Symbol) = string(s)[1] in 'A':'Z' ? s : Expr(:quote, s) + +function tbnew(s::Symbol) + istb(s) || return s + ts = map(symbol, split(string(s), "_")) + name = shift!(ts) + ts = map(totype, ts) + Expr(:$, :(Base.Meta.TypeBind($(Expr(:quote, name)), Set{Any}([$(ts...)])))) +end + +match_inner(b::TypeBind, ex, env) = + any(T -> (isa(T, Type) && isa(ex, T)) || isexpr(ex, T), b.ts) ? + (env[tbname(b)] = ex; env) : nomatch(b, ex) + +subtb(s) = s +subtb(s::Symbol) = tbnew(s) +subtb(s::Expr) = Expr(subtb(s.head), map(subtb, s.args)...) + +# @match macro + +allbindings(pat, bs) = + isbinding(pat) || (isslurp(pat) && pat ≠ :__) ? push!(bs, bname(pat)) : + istb(pat) ? push!(bs, tbname(pat)) : + isexpr(pat, :$) ? bs : + isa(pat, Expr) ? map(pat -> allbindings(pat, bs), [pat.head, pat.args...]) : + bs + +allbindings(pat) = (bs = Any[]; allbindings(pat, bs); bs) + +function bindinglet(bs, body) + ex = :(let $(esc(:env)) = env + $body + end) + for b in bs + push!(ex.args, :($(esc(b)) = env[$(Expr(:quote, b))])) + end + return ex +end + +function makeclause(line, els = nothing) + env = trymatch(:(pat_ -> yes_), line) + env == nothing && error("Invalid match clause $line") + pat, yes = env[:pat], env[:yes] + bs = allbindings(pat) + pat = subtb(pat) + quote + env = trymatch($(Expr(:quote, pat)), ex) + if env != nothing + $(bindinglet(bs, esc(yes))) + else + $els + end + end +end + +macro match(ex, lines) + @assert isexpr(lines, :block) + result = quote + ex = $(esc(ex)) + end + body = nothing + for line in reverse(rmlines(lines).args) + isline(result) && push!(result, line) + body = makeclause(line, body) + end + push!(result.args, body) + return result +end + +# Variable bindings as values + +moduleusings(mod) = ccall(:jl_module_usings, Any, (Any,), mod) + +function findsource(mod::Module, var::Symbol, seen = Set{Module}()) + mod in seen && return + var in names(mod, true) && return mod + push!(seen, mod) + sources = filter(m -> m ≠ nothing && !(m in seen), + map(m -> findsource(m, var, seen), + moduleusings(mod))) + isempty(sources) && return + return collect(sources)[1] +end + +immutable Binding + mod::Module + var::Symbol + + function Binding(mod::Module, var::Symbol) + mod′ = findsource(mod, var) + mod′ == nothing && error("$mod.$var not found") + return new(mod′, var) + end +end + +macro var(x) + (mod, x) = @match x begin + mod_.x_ -> (mod, x) + (mod_.@x_) -> (mod, x) + (@x_) -> (nothing, x) + x_"" -> (nothing, x) + _ -> isa(x, Symbol) ? (nothing, x) : + error("Invalid @var syntax `$x`") + end + mod == nothing && (mod = module_name(current_module())) + :(Binding($(esc(mod)), $(Expr(:quote, x)))) +end + +Base.show(io::IO, x::Binding) = println(io, "•$(x.mod).$(x.var)") + +Base.getindex(x::Binding) = x.mod.(x.var) + end # module diff --git a/test/docs.jl b/test/docs.jl index c47eae20fe678..8e0071d022f66 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -1,5 +1,7 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license +using Base.Meta + @doc "Doc abstract type" -> abstract C74685 <: AbstractArray @test stringmime("text/plain", Docs.doc(C74685))=="Doc abstract type\n" @@ -20,7 +22,7 @@ end @doc ("I am a macro";) :@ModuleMacroDoc.m @test (@doc ModuleMacroDoc) == "I am a module" -@test (@doc ModuleMacroDoc.@m) == ["I am a macro"] +@test (@doc ModuleMacroDoc.@m) == "I am a macro" # apropos function testing @@ -187,13 +189,13 @@ let TA = DocsTest.TA @test meta(DocsTest)[TA] == doc"TA" end -let mac = getfield(DocsTest, symbol("@mac")) +let mac = @var(DocsTest.@mac) funcdoc = meta(DocsTest)[mac] - @test funcdoc.main == doc"@mac" + @test funcdoc == doc"@mac" end -@test meta(DocsTest)[:G] == doc"G" -@test meta(DocsTest)[:K] == doc"K" +@test meta(DocsTest)[@var(DocsTest.G)] == doc"G" +@test meta(DocsTest)[@var(DocsTest.K)] == doc"K" # issue 11993 # Check if we are documenting the expansion of the macro diff --git a/test/replcompletions.jl b/test/replcompletions.jl index f9fa99607f860..2f7dce6f129e5 100644 --- a/test/replcompletions.jl +++ b/test/replcompletions.jl @@ -161,7 +161,7 @@ c,r,res = test_complete(s) s = "\\a" c, r, res = test_complete(s) -"\\alpha" in c +("\\alpha" in c) @test r == 1:2 @test s[r] == "\\a"