Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement expression matching and use it in the doc system #12088

Closed
wants to merge 12 commits into from
41 changes: 14 additions & 27 deletions base/Enums.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module Enums

using Base.Meta

export Enum, @enum

abstract Enum
Expand All @@ -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
Expand Down
6 changes: 1 addition & 5 deletions base/c.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
11 changes: 4 additions & 7 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)),
Expand Down
100 changes: 56 additions & 44 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module Docs

using Base.Meta
import Base.Markdown: @doc_str, MD

export doc
Expand Down Expand Up @@ -220,27 +221,20 @@ 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
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))))
Expand Down Expand Up @@ -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 "<doc string>" :@macro` or
# `@doc "<doc string>" :(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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@one-more-minute, from this comment, can (typealias T_ _) be rewritten as (typealias _ _)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that would be fine

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, had a moment of doubt after answering @ssfrr's question.


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
Expand Down Expand Up @@ -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
Expand Down
Loading