From 166735fbca570ef37ec3221dece1987564298043 Mon Sep 17 00:00:00 2001 From: Roger-luo Date: Thu, 19 Oct 2023 20:30:02 -0400 Subject: [PATCH] most of the patterns are implemented, start supporting splat, comprehension, array syntax --- src/data/emit/reflect.jl | 18 ++++ src/data/guess.jl | 17 ++- src/data/reflect.jl | 1 + src/match/data.jl | 20 ++-- src/match/emit/mod.jl | 221 ++++++++++++++++++++++++++++++++++++++- src/match/emit/tuple.jl | 0 src/match/mod.jl | 3 +- src/match/scan.jl | 6 -- 8 files changed, 263 insertions(+), 23 deletions(-) create mode 100644 src/match/emit/tuple.jl diff --git a/src/data/emit/reflect.jl b/src/data/emit/reflect.jl index ae9c7d6..1463505 100644 --- a/src/data/emit/reflect.jl +++ b/src/data/emit/reflect.jl @@ -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)) diff --git a/src/data/guess.jl b/src/data/guess.jl index 49bfbcd..8ef48dc 100644 --- a/src/data/guess.jl +++ b/src/data/guess.jl @@ -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 @@ -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 diff --git a/src/data/reflect.jl b/src/data/reflect.jl index 87f4590..20e1621 100644 --- a/src/data/reflect.jl +++ b/src/data/reflect.jl @@ -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() diff --git a/src/match/data.jl b/src/match/data.jl index 29a8a1b..ec93a3c 100644 --- a/src/match/data.jl +++ b/src/match/data.jl @@ -28,6 +28,16 @@ const JLType = Union{Symbol, Expr, DataType, UnionAll} xs::Vector{Pattern} end + # ... + struct Splat + body::Pattern + end + + struct TypeAnnotate + body::Pattern + type::JLType + end + struct Row xs::Vector{Pattern} end @@ -66,16 +76,6 @@ const JLType = Union{Symbol, Expr, DataType, UnionAll} xs::Vector{Pattern} end - # ... - struct Splat - body::Pattern - end - - struct TypeAnnotate - body::Pattern - type::JLType - end - struct Comprehension body::Pattern # generator end diff --git a/src/match/emit/mod.jl b/src/match/emit/mod.jl index 0a1be03..249968e 100644 --- a/src/match/emit/mod.jl +++ b/src/match/emit/mod.jl @@ -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 [...] has the following cases: + # 1. is defined, and is a type, typed vect + # 2. 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 diff --git a/src/match/emit/tuple.jl b/src/match/emit/tuple.jl new file mode 100644 index 0000000..e69de29 diff --git a/src/match/mod.jl b/src/match/mod.jl index 3126237..d569bb4 100644 --- a/src/match/mod.jl +++ b/src/match/mod.jl @@ -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") diff --git a/src/match/scan.jl b/src/match/scan.jl index 3b3986b..3c9d15b 100644 --- a/src/match/scan.jl +++ b/src/match/scan.jl @@ -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