-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
module RequiredKeywordArguments | ||
|
||
using Base.Meta | ||
export @required | ||
|
||
""" | ||
@required(arg) | ||
@required(arg::Type) | ||
Indicates that the given keyword argument is required; that is, an | ||
`ArgumentError` will be thrown if it is not provided. | ||
""" | ||
macro required(ex) | ||
target = if isexpr(ex, :(::)) | ||
ex.args[1] | ||
elseif isa(ex, Symbol) | ||
ex | ||
else | ||
throw(ArgumentError("@required($ex) does not match expected format")) | ||
end | ||
|
||
# TODO: fix when 0.5 support dropped | ||
# This is the right code, but it doesn't work on 0.5, so we make do with | ||
# the wronger version. | ||
# Expr(:kw, esc(ex), | ||
# :(throw(ArgumentError($"keyword argument $target is required")))) | ||
esc(Expr(:kw, ex, | ||
:(throw(ArgumentError($"keyword argument $target is required"))))) | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
""" | ||
Lint.SemanticASTs | ||
This module provides types for an annotated abstract syntax tree with nodes | ||
based off semantic information. | ||
""" | ||
module SemanticASTs | ||
|
||
using ..RequiredKeywordArguments | ||
|
||
using Base.Meta | ||
using Compat | ||
import Base.show | ||
|
||
# for code readability purposes | ||
const AST = Any | ||
|
||
export SemanticAST, FunctionName, InstanceFunctionName, TypeFunctionName, | ||
Signature, Quantification, Lambda, MethodDefinition, Unannotated, | ||
annotateⁿ, rawastⁿ | ||
|
||
""" | ||
SemanticAST | ||
An annotated expression. An expression is something which can be meaningfully | ||
evaluated into a value in a particular context. For example, `:(1 + 1)` and | ||
`:foo` are expressions. In Julia, even `type Foo end` is an expression, because | ||
it evaluates to `nothing` in the global context. | ||
""" | ||
@compat abstract type SemanticAST end | ||
|
||
rawastⁿ(ast::SemanticAST) = Nullable(rawast(ast)) | ||
nameⁿ(ast::SemanticAST) = Nullable(name(ast)) | ||
|
||
include("functionname.jl") | ||
include("functiondef.jl") | ||
|
||
immutable Unannotated <: SemanticAST | ||
_ast :: AST | ||
end | ||
rawast(ast::Unannotated) = ast._ast | ||
|
||
function show(io::IO, ast::Unannotated) | ||
print(io, "Unannotated(") | ||
show(io, get(rawastⁿ(ast))) | ||
print(io, ")") | ||
end | ||
|
||
""" | ||
SemanticAST(x::Expr) | ||
```jldoctest | ||
julia> SemanticAST(:(f(x) = x)) | ||
MethodDefinition( | ||
to=InstanceFunctionName(func=Unannotated(:f)), | ||
method=Lambda(signature=Signature(:((x,)))) | ||
) | ||
julia> functionname(:(function (::Type{Foo})(); Foo(1); end)) | ||
:Foo | ||
julia> functionname(:(function (p::Polynomial)(); p(0); end)) | ||
:(::Polynomial) | ||
julia> functionname(:(macro foo(); end)) | ||
Symbol("@foo") | ||
``` | ||
""" | ||
function SemanticAST(ex::Expr) | ||
if isexpr(ex, :function) | ||
# parse as function | ||
elseif isexpr(ex, :macro) | ||
else | ||
Unannotated(ex) | ||
end | ||
end | ||
|
||
SemanticAST(ex) = Unannotated(ex) | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
""" | ||
Signature | ||
The signature of a function. | ||
""" | ||
immutable Signature <: SemanticAST | ||
_ast :: AST | ||
end | ||
rawast(ast::Signature) = ast._ast | ||
|
||
function show(io::IO, ast::Signature) | ||
print(io, "Signature(") | ||
show(io, rawast(ast)) | ||
print(io, ")") | ||
end | ||
|
||
""" | ||
Quantification | ||
A parameter introduced by `where` in a method definition, including any bounds; | ||
for example, the `T <: Number` in `f(x::T, y::T) where T <: Number`. | ||
""" | ||
immutable Quantification <: SemanticAST | ||
_ast :: Nullable{AST} | ||
name :: Symbol | ||
lowerbound :: Nullable{SemanticAST} | ||
upperbound :: Nullable{SemanticAST} | ||
end | ||
name(ast::Quantification) = ast.name | ||
lowerboundⁿ(ast::Quantification) = ast.lowerbound | ||
upperboundⁿ(ast::Quantification) = ast.upperbound | ||
|
||
Quantification(ast=Nullable(); | ||
@required(name::Symbol), | ||
lowerbound=Nullable(), | ||
upperbound=Nullable() | ||
) = Quantification(ast, name, lowerbound, upperbound) | ||
|
||
function show(io::IO, ast::Quantification) | ||
print(io, "Quantification(name=") | ||
show(io, name(ast)) | ||
if !isnull(lowerboundⁿ(ast)) | ||
print(io, ", lowerbound=", lowerboundⁿ(ast)) | ||
end | ||
if !isnull(upperboundⁿ(ast)) | ||
print(io, ", upperbound=", upperboundⁿ(ast)) | ||
end | ||
print(io, ")") | ||
end | ||
|
||
function annotateⁿ(::Type{Quantification}, ast::AST)::Nullable | ||
name = if isa(ast, Symbol) | ||
Quantification(name=ast) | ||
elseif isexpr(ast, :comparison) && length(ast.args) == 5 && | ||
ast.args[2] == ast.args[4] == :(<:) && isa(ast.args[3], Symbol) | ||
Quantification(name=ast.args[3], | ||
lowerbound=SemanticAST(ast.args[1]), | ||
upperbound=SemanticAST(ast.args[5])) | ||
elseif isexpr(ast, :(<:)) && isa(ast.args[1], Symbol) | ||
Quantification(name=ast.args[1], upperbound=SemanticAST(ast.args[2])) | ||
elseif isexpr(ast, :(>:)) && isa(ast.args[1], Symbol) | ||
Quantification(name=ast.args[1], lowerbound=SemanticAST(ast.args[2])) | ||
else | ||
Nullable() | ||
end | ||
end | ||
|
||
""" | ||
FunctionDefinition | ||
A definition of a named or anonymous function. | ||
""" | ||
@compat abstract type FunctionDefinition <: SemanticAST end | ||
|
||
""" | ||
Lambda | ||
An anonymous function, including the signature, body, any static parameters, | ||
and return type declaration if applicable. | ||
""" | ||
immutable Lambda <: FunctionDefinition | ||
_ast :: Nullable{AST} | ||
sparams :: Vector{Quantification} | ||
signature :: Signature | ||
returntype :: Nullable{SemanticAST} | ||
code :: SemanticAST | ||
end | ||
rawastⁿ(ast::Lambda) = ast._ast | ||
sparams(ast::Lambda) = ast.sparams | ||
signature(ast::Lambda) = ast.signature | ||
returntypeⁿ(ast::Lambda) = ast.returntype | ||
code(ast::Lambda) = ast.code | ||
|
||
Lambda(ast=Nullable(); | ||
sparams=Quantification[], | ||
@required(signature::Signature), | ||
returntype=Nullable(), | ||
@required(code::SemanticAST) | ||
) = Lambda(ast, sparams, signature, returntype, code) | ||
|
||
function show(io::IO, ast::Lambda) | ||
print(io, "Lambda(") | ||
if !isempty(sparams(ast)) | ||
print("sparams=", sparams(ast), ", ") | ||
end | ||
print("signature=", signature(ast), ", ") | ||
if !isnull(returntypeⁿ(ast)) | ||
print("returntype=", get(returntype(ast)), ", ") | ||
end | ||
print("code=", code(ast), ")") | ||
end | ||
|
||
""" | ||
MethodDefinition | ||
A definition of a method, including the function name, signature, body, any | ||
static parameters, and return type declaration if applicable. | ||
""" | ||
immutable MethodDefinition <: FunctionDefinition | ||
_ast :: Nullable{AST} | ||
sparams :: Vector{Quantification} | ||
func :: FunctionName | ||
signature :: Signature | ||
returntype :: Nullable{SemanticAST} | ||
code :: SemanticAST | ||
end | ||
rawastⁿ(ast::MethodDefinition) = ast._ast | ||
sparams(ast::MethodDefinition) = ast.sparams | ||
func(ast::MethodDefinition) = ast.func | ||
signature(ast::MethodDefinition) = ast.signature | ||
returntypeⁿ(ast::MethodDefinition) = ast.returntype | ||
code(ast::MethodDefinition) = ast.code | ||
|
||
MethodDefinition(ast=Nullable(); | ||
sparams=Quantification[], | ||
@required(func::FunctionName), | ||
@required(signature::Signature), | ||
returntype=Nullable(), | ||
@required(code::SemanticAST) | ||
) = MethodDefinition(ast, sparams, func, signature, returntype, code) | ||
|
||
function show(io::IO, ast::MethodDefinition) | ||
print(io, "MethodDefinition(") | ||
if !isempty(sparams(ast)) | ||
print("sparams=", sparams(ast), ", ") | ||
end | ||
print("func=", func(ast), ", ") | ||
print("signature=", signature(ast), ", ") | ||
if !isnull(returntypeⁿ(ast)) | ||
print("returntype=", get(returntype(ast)), ", ") | ||
end | ||
print("code=", code(ast), ")") | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
""" | ||
FunctionName | ||
A function name is something that methods can validly be added to. | ||
""" | ||
@compat abstract type FunctionName <: SemanticAST end | ||
|
||
""" | ||
InstanceFunctionName | ||
A function name for a particular instance, as in `f(x)` or `Base.show(x, y)`. | ||
The instance here should represent a singleton, or a simple name in the case of | ||
a closure. | ||
""" | ||
immutable InstanceFunctionName <: FunctionName | ||
_ast :: Nullable{AST} | ||
instance :: SemanticAST | ||
end | ||
rawastⁿ(ast::InstanceFunctionName) = ast._ast | ||
instance(ast::InstanceFunctionName) = ast.instance | ||
|
||
InstanceFunctionName(ast=Nullable(); | ||
@required(instance::SemanticAST) | ||
) = InstanceFunctionName(ast, instance) | ||
|
||
function show(io::IO, ast::InstanceFunctionName) | ||
print(io, "InstanceFunctionName(instance=", instance(ast), ")") | ||
end | ||
|
||
""" | ||
TypeFunctionName | ||
A function name for a particular type, as in `(::Polynomial)(x)`. Optionally, | ||
the function name is given a name, like in `(p::Polynomial)(x)`. | ||
""" | ||
immutable TypeFunctionName <: FunctionName | ||
_ast :: Nullable{AST} | ||
name :: Nullable{Symbol} | ||
paramtype :: SemanticAST | ||
end | ||
rawastⁿ(ast::TypeFunctionName) = ast._ast | ||
nameⁿ(ast::TypeFunctionName) = ast.name | ||
paramtype(ast::TypeFunctionName) = ast.paramtype | ||
|
||
TypeFunctionName(ast=Nullable(); | ||
name=Nullable(), | ||
@required(paramtype::SemanticAST) | ||
) = TypeFunctionName(ast, name, paramtype) | ||
|
||
function show(io::IO, ast::TypeFunctionName) | ||
print(io, "TypeFunctionName(") | ||
if !isnull(nameⁿ(ast)) | ||
print(io, "name=") | ||
show(io, get(nameⁿ(ast))) | ||
print(io, ", ") | ||
end | ||
print(io, "paramtype=", paramtype(ast), ")") | ||
end | ||
|
||
""" | ||
annotateⁿ(FunctionName, ast) | ||
Return, wrapped in a `Nullable`, the given `ast` with extra semantic | ||
information computed given that `ast` represents the function name part of a | ||
method definition. If it cannot be understood that way, return `Nullable()`. | ||
""" | ||
function annotateⁿ(::Type{FunctionName}, ast::AST)::Nullable | ||
if isexpr(ast, :(::)) | ||
if length(ast.args) == 1 | ||
return TypeFunctionName(ast; paramtype=Unannotated(ast.args[1])) | ||
elseif length(ast.args) == 2 && isa(ast.args[1], Symbol) | ||
return TypeFunctionName(ast; name=ast.args[1], paramtype=Unannotated(ast.args[2])) | ||
end | ||
elseif isa(ast, Symbol) || isexpr(ast, :(.)) || isexpr(ast, :curly) | ||
return InstanceFunctionName(ast; instance=Unannotated(ast)) | ||
end | ||
Nullable() | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using Lint.RequiredKeywordArguments | ||
using Base.Test | ||
|
||
@testset "Required Arguments" begin | ||
foo1(; @required(bar)) = bar | ||
@test foo1(bar=42) == 42 | ||
@test_throws ArgumentError foo1() | ||
|
||
foo2(; @required(bar::Integer)) = bar | ||
@test foo2(bar=42) == 42 | ||
@test_throws TypeError foo2(bar="x") | ||
@test_throws ArgumentError foo2() | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters