|
1 | 1 | using Accessors
|
2 | 2 | using Accessors: ComposedOptic, PropertyLens, IndexLens, DynamicIndexLens
|
| 3 | +using JSON: JSON |
3 | 4 |
|
4 | 5 | const ALLOWED_OPTICS = Union{typeof(identity),PropertyLens,IndexLens,ComposedOptic}
|
5 | 6 |
|
@@ -302,7 +303,7 @@ subsumes(t::ComposedOptic, u::ComposedOptic) =
|
302 | 303 | # If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a
|
303 | 304 | # leaf of the "lens-tree".
|
304 | 305 | subsumes(t::ComposedOptic, u::PropertyLens) = false
|
305 |
| -# Here we need to check if `u.outer` (i.e. the next lens to be applied from `u`) is |
| 306 | +# Here we need to check if `u.inner` (i.e. the next lens to be applied from `u`) is |
306 | 307 | # subsumed by `t`, since this would mean that the rest of the composition is also subsumed
|
307 | 308 | # by `t`.
|
308 | 309 | subsumes(t::PropertyLens, u::ComposedOptic) = subsumes(t, u.inner)
|
@@ -752,3 +753,147 @@ function vsym(expr::Expr)
|
752 | 753 | error("Malformed variable name `$(expr)`!")
|
753 | 754 | end
|
754 | 755 | end
|
| 756 | + |
| 757 | +# String constants for each index type that we support serialisation / |
| 758 | +# deserialisation of |
| 759 | +const _BASE_INTEGER_TYPE = "Base.Integer" |
| 760 | +const _BASE_VECTOR_TYPE = "Base.Vector" |
| 761 | +const _BASE_UNITRANGE_TYPE = "Base.UnitRange" |
| 762 | +const _BASE_STEPRANGE_TYPE = "Base.StepRange" |
| 763 | +const _BASE_ONETO_TYPE = "Base.OneTo" |
| 764 | +const _BASE_COLON_TYPE = "Base.Colon" |
| 765 | +const _CONCRETIZED_SLICE_TYPE = "AbstractPPL.ConcretizedSlice" |
| 766 | +const _BASE_TUPLE_TYPE = "Base.Tuple" |
| 767 | + |
| 768 | +""" |
| 769 | + index_to_dict(::Integer) |
| 770 | + index_to_dict(::AbstractVector{Int}) |
| 771 | + index_to_dict(::UnitRange) |
| 772 | + index_to_dict(::StepRange) |
| 773 | + index_to_dict(::Colon) |
| 774 | + index_to_dict(::ConcretizedSlice{T, Base.OneTo{I}}) where {T, I} |
| 775 | + index_to_dict(::Tuple) |
| 776 | +
|
| 777 | +Convert an index `i` to a dictionary representation. |
| 778 | +""" |
| 779 | +index_to_dict(i::Integer) = Dict("type" => _BASE_INTEGER_TYPE, "value" => i) |
| 780 | +index_to_dict(v::Vector{Int}) = Dict("type" => _BASE_VECTOR_TYPE, "values" => v) |
| 781 | +index_to_dict(r::UnitRange) = Dict("type" => _BASE_UNITRANGE_TYPE, "start" => r.start, "stop" => r.stop) |
| 782 | +index_to_dict(r::StepRange) = Dict("type" => _BASE_STEPRANGE_TYPE, "start" => r.start, "stop" => r.stop, "step" => r.step) |
| 783 | +index_to_dict(r::Base.OneTo{I}) where {I} = Dict("type" => _BASE_ONETO_TYPE, "stop" => r.stop) |
| 784 | +index_to_dict(::Colon) = Dict("type" => _BASE_COLON_TYPE) |
| 785 | +index_to_dict(s::ConcretizedSlice{T,R}) where {T,R} = Dict("type" => _CONCRETIZED_SLICE_TYPE, "range" => index_to_dict(s.range)) |
| 786 | +index_to_dict(t::Tuple) = Dict("type" => _BASE_TUPLE_TYPE, "values" => map(index_to_dict, t)) |
| 787 | + |
| 788 | +""" |
| 789 | + dict_to_index(dict) |
| 790 | + dict_to_index(symbol_val, dict) |
| 791 | +
|
| 792 | +Convert a dictionary representation of an index `dict` to an index. |
| 793 | +
|
| 794 | +Users can extend the functionality of `dict_to_index` (and hence `VarName` |
| 795 | +de/serialisation) by extending this method along with [`index_to_dict`](@ref). |
| 796 | +Specifically, suppose you have a custom index type `MyIndexType` and you want |
| 797 | +to be able to de/serialise a `VarName` containing this index type. You should |
| 798 | +then implement the following two methods: |
| 799 | +
|
| 800 | +1. `AbstractPPL.index_to_dict(i::MyModule.MyIndexType)` should return a |
| 801 | + dictionary representation of the index `i`. This dictionary must contain the |
| 802 | + key `"type"`, and the corresponding value must be a string that uniquely |
| 803 | + identifies the index type. Generally, it makes sense to use the name of the |
| 804 | + type (perhaps prefixed with module qualifiers) as this value to avoid |
| 805 | + clashes. The remainder of the dictionary can have any structure you like. |
| 806 | +
|
| 807 | +2. Suppose the value of `index_to_dict(i)["type"]` is `"MyModule.MyIndexType"`. |
| 808 | + You should then implement the corresponding method |
| 809 | + `AbstractPPL.dict_to_index(::Val{Symbol("MyModule.MyIndexType")}, dict)`, |
| 810 | + which should take the dictionary representation as the second argument and |
| 811 | + return the original `MyIndexType` object. |
| 812 | +
|
| 813 | +To see an example of this in action, you can look in the the AbstractPPL test |
| 814 | +suite, which contains a test for serialising OffsetArrays. |
| 815 | +""" |
| 816 | +function dict_to_index(dict) |
| 817 | + t = dict["type"] |
| 818 | + if t == _BASE_INTEGER_TYPE |
| 819 | + return dict["value"] |
| 820 | + elseif t == _BASE_VECTOR_TYPE |
| 821 | + return collect(Int, dict["values"]) |
| 822 | + elseif t == _BASE_UNITRANGE_TYPE |
| 823 | + return dict["start"]:dict["stop"] |
| 824 | + elseif t == _BASE_STEPRANGE_TYPE |
| 825 | + return dict["start"]:dict["step"]:dict["stop"] |
| 826 | + elseif t == _BASE_ONETO_TYPE |
| 827 | + return Base.OneTo(dict["stop"]) |
| 828 | + elseif t == _BASE_COLON_TYPE |
| 829 | + return Colon() |
| 830 | + elseif t == _CONCRETIZED_SLICE_TYPE |
| 831 | + return ConcretizedSlice(Base.Slice(dict_to_index(dict["range"]))) |
| 832 | + elseif t == _BASE_TUPLE_TYPE |
| 833 | + return tuple(map(dict_to_index, dict["values"])...) |
| 834 | + else |
| 835 | + # Will error if the method is not defined, but this hook allows users |
| 836 | + # to extend this function |
| 837 | + return dict_to_index(Val(Symbol(t)), dict) |
| 838 | + end |
| 839 | +end |
| 840 | + |
| 841 | +optic_to_dict(::typeof(identity)) = Dict("type" => "identity") |
| 842 | +optic_to_dict(::PropertyLens{sym}) where {sym} = Dict("type" => "property", "field" => String(sym)) |
| 843 | +optic_to_dict(i::IndexLens) = Dict("type" => "index", "indices" => index_to_dict(i.indices)) |
| 844 | +optic_to_dict(c::ComposedOptic) = Dict("type" => "composed", "outer" => optic_to_dict(c.outer), "inner" => optic_to_dict(c.inner)) |
| 845 | + |
| 846 | +function dict_to_optic(dict) |
| 847 | + if dict["type"] == "identity" |
| 848 | + return identity |
| 849 | + elseif dict["type"] == "index" |
| 850 | + return IndexLens(dict_to_index(dict["indices"])) |
| 851 | + elseif dict["type"] == "property" |
| 852 | + return PropertyLens{Symbol(dict["field"])}() |
| 853 | + elseif dict["type"] == "composed" |
| 854 | + return dict_to_optic(dict["outer"]) ∘ dict_to_optic(dict["inner"]) |
| 855 | + else |
| 856 | + error("Unknown optic type: $(dict["type"])") |
| 857 | + end |
| 858 | +end |
| 859 | + |
| 860 | +varname_to_dict(vn::VarName) = Dict("sym" => getsym(vn), "optic" => optic_to_dict(getoptic(vn))) |
| 861 | + |
| 862 | +dict_to_varname(dict::Dict{<:AbstractString, Any}) = VarName{Symbol(dict["sym"])}(dict_to_optic(dict["optic"])) |
| 863 | + |
| 864 | +""" |
| 865 | + varname_to_string(vn::VarName) |
| 866 | +
|
| 867 | +Convert a `VarName` as a string, via an intermediate dictionary. This differs |
| 868 | +from `string(vn)` in that concretised slices are faithfully represented (rather |
| 869 | +than being pretty-printed as colons). |
| 870 | +
|
| 871 | +For `VarName`s which index into an array, this function will only work if the |
| 872 | +indices can be serialised. This is true for all standard Julia index types, but |
| 873 | +if you are using custom index types, you will need to implement the |
| 874 | +`index_to_dict` and `dict_to_index` methods for those types. See the |
| 875 | +documentation of [`dict_to_index`](@ref) for instructions on how to do this. |
| 876 | +
|
| 877 | +```jldoctest |
| 878 | +julia> varname_to_string(@varname(x)) |
| 879 | +"{\\"optic\\":{\\"type\\":\\"identity\\"},\\"sym\\":\\"x\\"}" |
| 880 | +
|
| 881 | +julia> varname_to_string(@varname(x.a)) |
| 882 | +"{\\"optic\\":{\\"field\\":\\"a\\",\\"type\\":\\"property\\"},\\"sym\\":\\"x\\"}" |
| 883 | +
|
| 884 | +julia> y = ones(2); varname_to_string(@varname(y[:])) |
| 885 | +"{\\"optic\\":{\\"indices\\":{\\"values\\":[{\\"type\\":\\"Base.Colon\\"}],\\"type\\":\\"Base.Tuple\\"},\\"type\\":\\"index\\"},\\"sym\\":\\"y\\"}" |
| 886 | +
|
| 887 | +julia> y = ones(2); varname_to_string(@varname(y[:], true)) |
| 888 | +"{\\"optic\\":{\\"indices\\":{\\"values\\":[{\\"range\\":{\\"stop\\":2,\\"type\\":\\"Base.OneTo\\"},\\"type\\":\\"AbstractPPL.ConcretizedSlice\\"}],\\"type\\":\\"Base.Tuple\\"},\\"type\\":\\"index\\"},\\"sym\\":\\"y\\"}" |
| 889 | +``` |
| 890 | +""" |
| 891 | +varname_to_string(vn::VarName) = JSON.json(varname_to_dict(vn)) |
| 892 | + |
| 893 | +""" |
| 894 | + string_to_varname(str::AbstractString) |
| 895 | +
|
| 896 | +Convert a string representation of a `VarName` back to a `VarName`. The string |
| 897 | +should have been generated by `varname_to_string`. |
| 898 | +""" |
| 899 | +string_to_varname(str::AbstractString) = dict_to_varname(JSON.parse(str)) |
0 commit comments