Skip to content

Commit

Permalink
most of the patterns are implemented, start supporting splat, compreh…
Browse files Browse the repository at this point in the history
…ension, array syntax
  • Loading branch information
Roger-luo committed Oct 20, 2023
1 parent fbfc3fa commit 166735f
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 23 deletions.
18 changes: 18 additions & 0 deletions src/data/emit/reflect.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ end
end
end

@pass function emit_variant_kind(info::EmitInfo)
body = foreach_variant(info, :tag) do variant::Variant, vinfo::VariantInfo
return QuoteNode(variant.kind)
end

return quote
function $Data.variant_kind(type::$(info.type.name))
$(emit_get_data_tag(info))
$body
end

function $Data.variant_kind(variant_type::$(info.type.variant))
tag = variant_type.tag
$body
end
end
end

@pass function emit_variant_type(info::EmitInfo)
return quote
function $Data.variant_type(type::$(info.type.name))
Expand Down
17 changes: 15 additions & 2 deletions src/data/guess.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ function materialize_self(mod::Module, expr, self; source=nothing)
end
end

function guess_module(mod::Module, expr)
Meta.isexpr(expr, :.) || return mod
if expr isa Symbol
isdefined(mod, expr) || throw(SyntaxError("unknown module: $expr"))
return getfield(mod, expr)
elseif Meta.isexpr(expr.args[1], :.)
submod = guess_module(mod, expr.args[1])
return guess_module(submod, expr.args[2].value)
else
throw(SyntaxError("invalid module: $expr"))
end
end

function guess_type(mod::Module, expr; source=nothing)
expr isa Type && return expr
expr isa SelfType && return Any # always box self reference
Expand Down Expand Up @@ -45,10 +58,10 @@ function guess_type(mod::Module, expr; source=nothing)
return expr
end
elseif Meta.isexpr(expr, :.)
submod = guess_type(mod, expr.args[1])
submod = guess_module(mod, expr.args[1])
return guess_type(submod, expr.args[2].value)
else
throw(SyntaxError("invalid type: $expr"; source))
return expr
end
end

Expand Down
1 change: 1 addition & 0 deletions src/data/reflect.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ is_datatype(variant_instance_or_type)::Bool = false
data_type_name(variant_instance_or_type)::Symbol = invalid_method()
data_type_module(variant_instance_or_type)::Module = invalid_method()
variant_name(variant_instance_or_type)::Symbol = invalid_method()
variant_kind(variant_instance_or_type)::VariantKind = invalid_method()
variant_type(variant_instance) = invalid_method()
variant_storage(variant_instance) = invalid_method()
variant_tag(variant_instance)::UInt8 = invalid_method()
Expand Down
20 changes: 10 additions & 10 deletions src/match/data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ const JLType = Union{Symbol, Expr, DataType, UnionAll}
xs::Vector{Pattern}
end

# <splat>...
struct Splat
body::Pattern
end

struct TypeAnnotate
body::Pattern
type::JLType
end

struct Row
xs::Vector{Pattern}
end
Expand Down Expand Up @@ -66,16 +76,6 @@ const JLType = Union{Symbol, Expr, DataType, UnionAll}
xs::Vector{Pattern}
end

# <splat>...
struct Splat
body::Pattern
end

struct TypeAnnotate
body::Pattern
type::JLType
end

struct Comprehension
body::Pattern # generator
end
Expand Down
221 changes: 217 additions & 4 deletions src/match/emit/mod.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,224 @@
function emit(info::EmitInfo)
quote
let $(info.value_holder) = $(info.value)
if cond
body
matches = expr_map(info.patterns, info.exprs) do pat, expr
cond, assigns = emit_decons(info, pat)

quote
if $cond
$(info.return_var) = let $(assigns...)
$expr
end
@goto $(info.final_label)
end
end
end

quote
$(info.value_holder) = $(info.value)
$matches
@label $(info.final_label)
$(info.return_var)
end
end

and_expr(lhs, rhs) = quote
$lhs && $rhs
end

struct PatternInfo
emit::EmitInfo
scope::Dict{Symbol, Set{Symbol}}
end

PatternInfo(info::EmitInfo) = PatternInfo(info, Dict{Symbol, Set{Symbol}}())

function Base.setindex!(info::PatternInfo, v::Symbol, k::Symbol)
push!(get!(Set{Symbol}, info.scope, k), v)
return info
end

function decons(info::PatternInfo, pat::Pattern.Type)
return function value_assigned(x)
@gensym value
return quote
$value = $x
$(inner_decons(info, pat)(value))
end
end
end

function inner_decons(info::PatternInfo, pat::Pattern.Type)
isa_variant(pat, Pattern.Wildcard) && return decons_wildcard(info, pat)
isa_variant(pat, Pattern.Variable) && return decons_variable(info, pat)
isa_variant(pat, Pattern.Quote) && return decons_quote(info, pat)
isa_variant(pat, Pattern.And) && return decons_and(info, pat)
isa_variant(pat, Pattern.Or) && return decons_or(info, pat)
isa_variant(pat, Pattern.Ref) && return decons_ref(info, pat)
isa_variant(pat, Pattern.Call) && return decons_call(info, pat)
isa_variant(pat, Pattern.Tuple) && return decons_tuple(info, pat)
isa_variant(pat, Pattern.Vector) && return decons_untyped_vect(info, pat)
isa_variant(pat, Pattern.Splat) && return decons_splat(info, pat)
isa_variant(pat, Pattern.TypeAnnotate) && return decons_type_annotate(info, pat)

error("invalid pattern: $pat")
end

function decons_wildcard(info::PatternInfo, pat::Pattern.Type)
return function wildcard(value)
return true
end
end

function decons_variable(info::PatternInfo, pat::Pattern.Type)
return function variable(value)
# NOTE: this is used to create a scope
# using let ... end later, so we cannot
# directly assign it to the pattern variable
placeholder = gensym()
info[pat.:1] = placeholder
return quote
$(placeholder) = $value
true
end
end
end

function decons_quote(info::PatternInfo, pat::Pattern.Type)
return function _quote(value)
return quote
$value == $(pat.:1)
end
end
end

function decons_and(info::PatternInfo, pat::Pattern.Type)
return function and(value)
return quote
$(decons(info, pat.:1)(value)) && $(decons(info, pat.:2)(value))
end
end
end

function decons_or(info::PatternInfo, pat::Pattern.Type)
return function or(value)
return quote
$(decons(info, pat.:1)(value)) || $(decons(info, pat.:2)(value))
end
end
end

function decons_ref(info::PatternInfo, pat::Pattern.Type)
# NOTE: we generate both cases here, because Julia should
# be able to eliminate one of the branches during compile
# NOTE: ref syntax <symbol> [<elem>...] has the following cases:
# 1. <symbol> is defined, and is a type, typed vect
# 2. <symbol> is not defined in global scope as type,
# but is defined as a variable, getindex, the match
# will try to find the index that returns the input
# value.
# 2 is not supported for now because I don't see any use case.
return decons_vect(info, pat, :($value isa $Base.Vector{$head}))
end

function decons_call(info::PatternInfo, pat::Pattern.Type)
# NOTE: when we see a call, it can only be a constructor
# because our syntactical pattern match is performed on data only
# args => get field by index, then compare
# kwargs => get field by name, then compare

# struct Call
# head # must be constant object
# args::Vector{Pattern}
# kwargs::Dict{Symbol, Pattern}
# end

# we need to special case data type because Julia types
# do not share a common interface with our ADT for getting
# numbered fields, e.g getproperty(x, ::Int) is not defined for
# Julia types in general.
@gensym value
nfields = length(pat.args) + length(pat.kwargs)
head = Base.eval(info.emit.mod, pat.head)
if Data.is_datatype(head) # check if our pattern is correct
Data.variant_nfields(head) >= nfields || throw(SyntaxError("invalid pattern: $pat"))
Data.variant_kind(head) == Data.Anonymous && length(pat.kwargs) > 0 && throw(SyntaxError("invalid pattern: $pat"))

type_assert = :($Data.isa_variant($value, $head))
args_conds = mapfoldl(and_expr, enumerate(pat.args), init=true) do (idx, x)
decons(info, x)(:($Base.getproperty($value, $idx)))
end
kwargs_conds = mapfoldl(and_expr, pat.kwargs, init=true) do kw
key, val = kw
decons(info, val)(:($Base.getproperty($value, $key)))
end
else
Base.fieldcount(head) >= nfields || throw(SyntaxError("too many fields to match: $pat"))

type_assert = :($value isa $head)
args_conds = mapfoldl(and_expr, enumerate(pat.args), init=true) do (idx, x)
decons(info, x)(:($Core.getfield($value, $idx)))
end
kwargs_conds = mapfoldl(and_expr, pat.kwargs, init=true) do kw
key, val = kw
decons(info, val)(:($Core.getfield($value, $key)))
end
end

return function call(x)
return quote
$value = $x
$type_assert && $args_conds && $kwargs_conds
end
end
end

function decons_tuple(info::PatternInfo, pat::Pattern.Type)
type_params = [isa_variant(x, Pattern.Quote) ? :($Base.typeof($(x.:1))) : Any for x in pat.xs]
type = :($Base.Tuple{$(type_params...)})

return function _tuple(value)
return mapfoldl(and_expr, enumerate(pat.xs), init=:($value isa $type)) do (idx, x)
decons(info, x)(:($value[$idx]))
end
end
end

function decons_untyped_vect(info::PatternInfo, pat::Pattern.Type)
return decons_vect(info, pat, true)
end

function decons_vect(info::PatternInfo, pat::Pattern.Type, type)
return function ref(value)
@gensym head

vect_decons = mapfoldl(
and_expr,
enumerate(pat.args),
init=type
) do (idx, x)
decons(info, x)(:($value[$idx]))
end

return quote
$head = $(pat.head)
if $head isa Type
$vect_decons
else
throw(ArgumentError("invalid type: $($head)"))
end
end
end
end

function decons_splat(info::EmitInfo, pat::Pattern.Type)
return function splat(value)
end
end

function decons_type_annotate(info::EmitInfo, pat::Pattern.Type)
return function annotate(value)
and_expr(
:($value isa $(pat.type)),
decons(info, pat.body)(value)
)
end
end
Empty file added src/match/emit/tuple.jl
Empty file.
3 changes: 2 additions & 1 deletion src/match/mod.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Match

using Liang.Data: Data, @data, isa_variant, SyntaxError
using ExproniconLite: expr_map
using Liang.Data: Data, @data, isa_variant, SyntaxError, guess_type

include("data.jl")
include("scan.jl")
Expand Down
6 changes: 0 additions & 6 deletions src/match/scan.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,3 @@ function call2pattern(expr)
kwargs,
)
end

function guess_module(mod::Module, expr)
Meta.isexpr(expr, :.) || return mod
submod = guess_module(mod, expr.args[1])
return guess_module(submod, expr.args[2].value)
end

0 comments on commit 166735f

Please sign in to comment.