destructure 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/destructure.rb +1 -0
- data/lib/destructure/destructure.rb +90 -0
- data/lib/destructure/dmatch.rb +160 -0
- data/lib/destructure/env.rb +49 -0
- data/lib/destructure/magic.rb +26 -0
- data/lib/destructure/sexp_transformer.rb +156 -0
- data/lib/destructure/types.rb +90 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5d1c33debe9be83eab196be5c71b8602b4467a0e
|
4
|
+
data.tar.gz: d28c21e0de1f1ddd88bbfa73a858025375f13fba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d4266394d4d8ee34872c810c777ca1882a43f6cc1d769f62855ef8e5eb16e38b4a3cf10c09ee1a0c7418932e6faba1a2eb6fa226b08a3ea4952b3217158e38bb
|
7
|
+
data.tar.gz: 52ede93459670e44d1407263210267aedae88a6a69f184fb271e24ef709b5148675f251644e34ccc02b00bd94b2cd7584ccb12eaa3eb63773fd1f44aa5f8f802
|
data/lib/destructure.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative './destructure/destructure'
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'sourcify'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'paramix'
|
4
|
+
require 'binding_of_caller'
|
5
|
+
require 'destructure/dmatch'
|
6
|
+
require 'destructure/sexp_transformer'
|
7
|
+
|
8
|
+
module Destructure
|
9
|
+
|
10
|
+
include Paramix::Parametric
|
11
|
+
|
12
|
+
parameterized do |params|
|
13
|
+
|
14
|
+
private :bind_locals do
|
15
|
+
bind = params[:bind_locals]
|
16
|
+
@bind_locals ||= bind.nil? ? true : bind
|
17
|
+
end
|
18
|
+
|
19
|
+
if params[:env_name]
|
20
|
+
private params[:env_name] do
|
21
|
+
@_my_destructure_env
|
22
|
+
end
|
23
|
+
private :set_custom_env do |value|
|
24
|
+
@_my_destructure_env = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if params[:matcher_name]
|
29
|
+
private params[:matcher_name] do |&pattern|
|
30
|
+
proc { |x| dbind(x, &pattern) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def dbind(x, &pat_block)
|
36
|
+
dbind_internal(x, pat_block.to_sexp(strip_enclosure: true, ignore_nested: true), binding.of_caller(1), caller_locations(1,1)[0].label)
|
37
|
+
end
|
38
|
+
|
39
|
+
private ########################################
|
40
|
+
|
41
|
+
def bind_locals
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def dbind_internal(x, sexp, caller_binding, caller_location)
|
46
|
+
env = dbind_no_ostruct_sexp(x, sexp, caller_binding)
|
47
|
+
return nil if env.nil?
|
48
|
+
|
49
|
+
if bind_locals
|
50
|
+
env.keys.each {|k| _destructure_set(k.name, env[k], caller_binding, caller_location)}
|
51
|
+
end
|
52
|
+
|
53
|
+
ostruct_env = env.to_openstruct
|
54
|
+
set_custom_env(ostruct_env) if self.respond_to?(:set_custom_env, true)
|
55
|
+
ostruct_env
|
56
|
+
end
|
57
|
+
|
58
|
+
def dbind_no_ostruct_sexp(x, sexp, caller_binding)
|
59
|
+
sp = sexp
|
60
|
+
pat = SexpTransformer.transform(sp, caller_binding)
|
61
|
+
DMatch::match(pat, x)
|
62
|
+
end
|
63
|
+
|
64
|
+
def _destructure_set(name, value, binding, caller)
|
65
|
+
if name.is_a?(String) || binding.eval("defined? #{name}") == 'local-variable'
|
66
|
+
$binding_temp = value
|
67
|
+
binding.eval("#{name} = $binding_temp")
|
68
|
+
else
|
69
|
+
if binding.eval('self').respond_to?(name, true)
|
70
|
+
raise "Cannot have pattern variable named '#{name}'. A method already exists with that name. Choose a different name, " +
|
71
|
+
"or pre-initialize a local variable that shadows the method."
|
72
|
+
end
|
73
|
+
@_destructure_env ||= {}
|
74
|
+
@_destructure_env[caller] ||= {}
|
75
|
+
@_destructure_env[caller][name] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def method_missing(name, *args, &block)
|
80
|
+
if bind_locals
|
81
|
+
c = caller_locations(1,1)[0].label
|
82
|
+
@_destructure_env ||= {}
|
83
|
+
caller_hash = @_destructure_env[c]
|
84
|
+
caller_hash && caller_hash.keys.include?(name) ? caller_hash[name] : super
|
85
|
+
else
|
86
|
+
super
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'destructure/env'
|
2
|
+
require 'destructure/types'
|
3
|
+
|
4
|
+
class DMatch
|
5
|
+
def self.match(pat, x)
|
6
|
+
DMatch.new(Env.new).match(pat, x)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self._
|
10
|
+
Wildcard.instance
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(env)
|
14
|
+
@env = env
|
15
|
+
end
|
16
|
+
|
17
|
+
def match(pat, x)
|
18
|
+
case
|
19
|
+
when pat.is_a?(Wildcard); @env
|
20
|
+
when pat.is_a?(Pred) && pat.test(x, @env); @env
|
21
|
+
when pat.is_a?(FilterSplat); match_filter_splat(pat, x)
|
22
|
+
when pat.is_a?(SelectSplat); match_select_splat(pat, x)
|
23
|
+
when pat.is_a?(Splat); match_splat(pat, x)
|
24
|
+
when pat.is_a?(Var) && pat.test(x, @env); match_var(pat, x)
|
25
|
+
when pat.is_a?(Obj) && pat.test(x, @env) && all_field_patterns_match(pat, x); @env
|
26
|
+
when pat.is_a?(String) && pat == x; @env
|
27
|
+
when pat.is_a?(Regexp); match_regexp(pat, x)
|
28
|
+
when pat.is_a?(Or); match_or(pat, x)
|
29
|
+
when hash(pat, x) && all_keys_match(pat, x); @env
|
30
|
+
when enumerable(pat, x); match_enumerable(pat, x)
|
31
|
+
when pat == x; @env
|
32
|
+
else; nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private ###########################################################
|
37
|
+
|
38
|
+
def all_keys_match(pat, x)
|
39
|
+
all_match(pat.keys.map { |k| x.keys.include?(k) && match(pat[k], x[k]) })
|
40
|
+
end
|
41
|
+
|
42
|
+
def match_regexp(pat, x)
|
43
|
+
m = pat.match(x)
|
44
|
+
m && @env.merge!(Hash[pat.named_captures.keys.map { |k| [Var.new(k.to_sym), m[k]] }])
|
45
|
+
end
|
46
|
+
|
47
|
+
def all_field_patterns_match(pat, x)
|
48
|
+
all_match(pat.fields.keys.map { |name| x.respond_to?(name) && match(pat.fields[name], x.send(name)) })
|
49
|
+
end
|
50
|
+
|
51
|
+
def match_var(pat, x)
|
52
|
+
@env.bind(pat, x)
|
53
|
+
end
|
54
|
+
|
55
|
+
def match_or(pat, x)
|
56
|
+
pat.patterns.lazy.map{|p| match(p, x)}.reject{|e| e.nil?}.first
|
57
|
+
end
|
58
|
+
|
59
|
+
def match_splat(pat, x)
|
60
|
+
@env.bind(pat, enumerable(x) ? x : [x])
|
61
|
+
end
|
62
|
+
|
63
|
+
def match_select_splat(pat, x)
|
64
|
+
x_match_and_env = x.map { |z| [z, DMatch::match(pat.pattern, z)] }.reject { |q| q.last.nil? }.first
|
65
|
+
if x_match_and_env
|
66
|
+
x_match, env = x_match_and_env
|
67
|
+
@env.bind(pat, x_match) && @env.merge!(env)
|
68
|
+
else
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def match_filter_splat(pat, x)
|
74
|
+
@env.bind(pat, x.map { |z| [z, match(pat.pattern, z)] }.reject { |q| q.last.nil? }.map { |q| q.first })
|
75
|
+
end
|
76
|
+
|
77
|
+
def match_enumerable(pat, x)
|
78
|
+
case
|
79
|
+
when (parts = decompose_splatted_enumerable(pat))
|
80
|
+
pat_before, pat_splat, pat_after = parts
|
81
|
+
x_before = x.take(pat_before.length)
|
82
|
+
if pat_after.any?
|
83
|
+
splat_len = len(x) - pat_before.length - pat_after.length
|
84
|
+
return nil if splat_len < 0
|
85
|
+
x_splat = x.drop(pat_before.length).take(splat_len)
|
86
|
+
else
|
87
|
+
x_splat = x.drop(pat_before.length)
|
88
|
+
end
|
89
|
+
|
90
|
+
before_and_splat_result = match_enumerable_no_splats(pat_before, x_before) && match(pat_splat, x_splat)
|
91
|
+
|
92
|
+
if before_and_splat_result && pat_after.any?
|
93
|
+
# do this only if we have to, since it requires access to the end of the enumerable,
|
94
|
+
# which doesn't work with infinite enumerables
|
95
|
+
x_after = take_last(pat_after.length, x)
|
96
|
+
match_enumerable_no_splats(pat_after, x_after)
|
97
|
+
else
|
98
|
+
before_and_splat_result
|
99
|
+
end
|
100
|
+
when len(pat) == len(x)
|
101
|
+
match_enumerable_no_splats(pat, x)
|
102
|
+
else; nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def decompose_splatted_enumerable(pat)
|
107
|
+
before = []
|
108
|
+
splat = nil
|
109
|
+
after = []
|
110
|
+
pat.each do |p|
|
111
|
+
case
|
112
|
+
when p.is_a?(Splat)
|
113
|
+
if splat.nil?
|
114
|
+
splat = p
|
115
|
+
else
|
116
|
+
raise "cannot have more than one splat in a single array: #{pat.inspect}"
|
117
|
+
end
|
118
|
+
when splat.nil?
|
119
|
+
before.push(p)
|
120
|
+
else
|
121
|
+
after.push(p)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
splat && [before, splat, after]
|
126
|
+
end
|
127
|
+
|
128
|
+
def take_last(n, xs)
|
129
|
+
result = []
|
130
|
+
xs.reverse_each do |x|
|
131
|
+
break if result.length == n
|
132
|
+
result.unshift x
|
133
|
+
end
|
134
|
+
result
|
135
|
+
end
|
136
|
+
|
137
|
+
def len(x)
|
138
|
+
x.respond_to?(:length) ? x.length : x.count
|
139
|
+
end
|
140
|
+
|
141
|
+
def match_enumerable_no_splats(pat, x)
|
142
|
+
all_match(pat.zip(x).map{|a| match(*a)}) ? @env : nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def enumerable(*xs)
|
146
|
+
xs.all?{|x| x.is_a?(Enumerable)}
|
147
|
+
end
|
148
|
+
|
149
|
+
def hash(*xs)
|
150
|
+
xs.all?{|x| x.is_a?(Hash)}
|
151
|
+
end
|
152
|
+
|
153
|
+
def all_match(xs)
|
154
|
+
xs.all?{|x| x.is_a?(Env)}
|
155
|
+
end
|
156
|
+
|
157
|
+
class Wildcard
|
158
|
+
include Singleton
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'destructure/dmatch'
|
3
|
+
require 'destructure/types'
|
4
|
+
|
5
|
+
class DMatch
|
6
|
+
class Env
|
7
|
+
|
8
|
+
def env
|
9
|
+
@env ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](identifier)
|
13
|
+
raise 'identifier must be a Var or symbol' unless (identifier.is_a? Var) || (identifier.is_a? Symbol)
|
14
|
+
if identifier.is_a? Symbol
|
15
|
+
identifier = env.keys.select{|k| k.name == identifier}.first || identifier
|
16
|
+
end
|
17
|
+
v = env[identifier]
|
18
|
+
raise "Identifier '#{identifier}' is not bound." if v.nil?
|
19
|
+
v.is_a?(EnvNil) ? nil : v
|
20
|
+
end
|
21
|
+
|
22
|
+
def bind(identifier, value)
|
23
|
+
raise 'identifier must be a Var' unless identifier.is_a? Var
|
24
|
+
value_to_store = value.nil? ? EnvNil.new : value
|
25
|
+
existing_key = env.keys.select{|k| k == identifier || (k.name.is_a?(Symbol) && k.name == identifier.name)}.first
|
26
|
+
return nil if existing_key &&
|
27
|
+
(DMatch.match(env[existing_key], value_to_store).nil? ||
|
28
|
+
DMatch.match(value_to_store, env[existing_key]).nil?)
|
29
|
+
env[existing_key || identifier] = value_to_store
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
alias []= bind
|
34
|
+
|
35
|
+
def keys
|
36
|
+
env.keys
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_openstruct
|
40
|
+
OpenStruct.new(Hash[env.map{|kv| [kv.first.name, kv.last]}])
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge!(other_env)
|
44
|
+
other_env.keys.any?{|k| bind(k, other_env[k]).nil?} ? nil : self
|
45
|
+
end
|
46
|
+
|
47
|
+
class EnvNil; end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'destructure/destructure'
|
2
|
+
|
3
|
+
module DestructureMagic
|
4
|
+
def self.included(base)
|
5
|
+
orig = base.instance_method(:=~)
|
6
|
+
|
7
|
+
base.send(:define_method, :=~) do |pattern|
|
8
|
+
if pattern.is_a?(Regexp)
|
9
|
+
orig.bind(self).call(pattern)
|
10
|
+
elsif pattern.is_a?(Proc)
|
11
|
+
# stuff gets cranky if you try to factor this out
|
12
|
+
caller_binding = binding.of_caller(1)
|
13
|
+
caller_location = caller_locations(1,1)[0].label
|
14
|
+
caller = caller_binding.eval('self')
|
15
|
+
caller.class.send(:include, Destructure) unless caller.class.included_modules.include?(Destructure)
|
16
|
+
caller.send(:dbind_internal, self, pattern.to_sexp(strip_enclosure: true, ignore_nested: true), caller_binding, caller_location)
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Object; include DestructureMagic end
|
25
|
+
class String; include DestructureMagic end
|
26
|
+
class Symbol; include DestructureMagic end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'destructure/dmatch'
|
3
|
+
|
4
|
+
module Destructure
|
5
|
+
class SexpTransformer
|
6
|
+
|
7
|
+
def self.transform(sp, caller_binding)
|
8
|
+
SexpTransformer.new(caller_binding).transform(sp)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(caller_binding)
|
12
|
+
@caller_binding = caller_binding
|
13
|
+
end
|
14
|
+
|
15
|
+
def transform(sp)
|
16
|
+
_ = DMatch::_
|
17
|
+
klass_sym = DMatch::Var.new(&method(:is_constant?))
|
18
|
+
case
|
19
|
+
# '_' (wildcard)
|
20
|
+
when e = dmatch([:call, _, :_, _], sp); _
|
21
|
+
# object matcher without parameters
|
22
|
+
when e = dmatch([:const, klass_sym], sp)
|
23
|
+
make_obj(e[klass_sym], {})
|
24
|
+
# '~' (splat)
|
25
|
+
when e = dmatch([:call, var(:identifier_sexp), :~, [:arglist]], sp); splat(unwind_receivers_and_clean(e[:identifier_sexp]))
|
26
|
+
# '!' (variable value)
|
27
|
+
when e = dmatch([:not, var(:value_sexp)], sp)
|
28
|
+
@caller_binding.eval(unwind_receivers_and_clean(e[:value_sexp]).to_s)
|
29
|
+
# '|' (alternative patterns)
|
30
|
+
when e = dmatch([:call, var(:rest), :|, [:arglist, var(:alt)]], sp); DMatch::Or.new(*[e[:rest], e[:alt]].map(&method(:transform)))
|
31
|
+
# generic call
|
32
|
+
when e = dmatch([:call, var(:receiver), var(:msg), var(:arglist)], sp)
|
33
|
+
transform_call(e[:receiver], e[:msg], e[:arglist])
|
34
|
+
# instance variable
|
35
|
+
when e = dmatch([:ivar, var(:name)], sp); var(e[:name].to_s)
|
36
|
+
# let
|
37
|
+
# ... with local or instance vars
|
38
|
+
when e = dmatch([DMatch::Or.new(:lasgn, :iasgn), var(:lhs), var(:rhs)], sp)
|
39
|
+
let_var(e[:lhs], transform(e[:rhs]))
|
40
|
+
# ... with attributes or something more complicated
|
41
|
+
when e = dmatch([:attrasgn, var(:obj), var(:attr), [:arglist, var(:rhs)]], sp)
|
42
|
+
var_name = unwind_receivers_and_clean([:call, e[:obj], e[:attr].to_s.sub(/=$/,'').to_sym, [:arglist]])
|
43
|
+
let_var(var_name, transform(e[:rhs]))
|
44
|
+
# literal values
|
45
|
+
when e = dmatch([:lit, var(:value)], sp); e[:value]
|
46
|
+
when e = dmatch([:true], sp); true
|
47
|
+
when e = dmatch([:false], sp); false
|
48
|
+
when e = dmatch([:nil], sp); nil
|
49
|
+
when e = dmatch([:str, var(:s)], sp); e[:s]
|
50
|
+
when e = dmatch([:array, splat(:items)], sp); e[:items].map(&method(:transform))
|
51
|
+
when e = dmatch([:hash, splat(:kvs)], sp); Hash[*e[:kvs].map(&method(:transform))]
|
52
|
+
else; raise "Unexpected sexp: #{sp.inspect}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private ########################################
|
57
|
+
|
58
|
+
def transform_call(*sexp_call)
|
59
|
+
sexp_receiver, sexp_msg, sexp_args = sexp_call
|
60
|
+
_ = DMatch::_
|
61
|
+
klass_sym_var = DMatch::Var.new(&method(:is_constant?))
|
62
|
+
case
|
63
|
+
# Class[...]
|
64
|
+
when e = dmatch([[:const, klass_sym_var], :[]], [sexp_receiver, sexp_msg])
|
65
|
+
field_map = make_field_map(sexp_args)
|
66
|
+
klass_sym = e[klass_sym_var]
|
67
|
+
klass_sym == :Hash ? field_map : make_obj(klass_sym, field_map)
|
68
|
+
# local variable
|
69
|
+
when e = dmatch([nil, var(:name), [:arglist]], sexp_call); var(e[:name])
|
70
|
+
# call chain (@one.two(12).three[3].four)
|
71
|
+
else; var(unwind_receivers_and_clean([:call, *sexp_call]))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def make_field_map(sexp_args)
|
76
|
+
case
|
77
|
+
# Class[a: 1, b: 2]
|
78
|
+
when e = dmatch([:arglist, [:hash, splat(:kv_sexps)]], sexp_args)
|
79
|
+
kvs = transform_many(e[:kv_sexps])
|
80
|
+
Hash[*kvs]
|
81
|
+
# Class[a, b, c]
|
82
|
+
when e = dmatch([:arglist, splat(:field_name_sexps)], sexp_args)
|
83
|
+
field_names = transform_many(e[:field_name_sexps])
|
84
|
+
Hash[field_names.map { |f| [f.name, var(f.name)] }]
|
85
|
+
else; raise 'oops'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def unwind_receivers_and_clean(receiver)
|
90
|
+
unwound = unwind_receivers(receiver).gsub(/\.$/, '').gsub(/\.\[/, '[')
|
91
|
+
identifier?(unwound) ? unwound.to_sym : unwound
|
92
|
+
end
|
93
|
+
|
94
|
+
def identifier?(x)
|
95
|
+
x =~ /^[_a-zA-Z][_0-9a-zA-Z]*$/
|
96
|
+
end
|
97
|
+
|
98
|
+
def unwind_receivers(receiver)
|
99
|
+
to_s
|
100
|
+
case
|
101
|
+
when receiver.nil?; ''
|
102
|
+
when e = dmatch([:lit, var(:value)], receiver); "#{e[:value]}."
|
103
|
+
when e = dmatch([:ivar, var(:name)], receiver); "#{e[:name]}."
|
104
|
+
when e = dmatch([:call, var(:receiver), :[], [:arglist, splat(:args)]], receiver)
|
105
|
+
unwind_receivers(e[:receiver]) + format_hash_call(e[:args])
|
106
|
+
when e = dmatch([:call, var(:receiver), var(:msg), [:arglist, splat(:args)]], receiver)
|
107
|
+
unwind_receivers(e[:receiver]) + format_method_call(e[:msg], e[:args])
|
108
|
+
else; raise 'oops'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def format_method_call(msg, args)
|
113
|
+
"#{msg}(#{transform_args(args)})".gsub(/\(\)$/, '') + '.'
|
114
|
+
end
|
115
|
+
|
116
|
+
def format_hash_call(args)
|
117
|
+
"[#{transform_args(args)}].".gsub(/\(\)$/, '')
|
118
|
+
end
|
119
|
+
|
120
|
+
def transform_args(args)
|
121
|
+
transform_many(args).map { |x| x.is_a?(Symbol) ? ":#{x}" : x.to_s }.join(', ')
|
122
|
+
end
|
123
|
+
|
124
|
+
def transform_many(xs)
|
125
|
+
xs.map(&method(:transform))
|
126
|
+
end
|
127
|
+
|
128
|
+
def make_obj(klass_sym, field_map)
|
129
|
+
DMatch::Obj.of_type(klass_sym.to_s.constantize, field_map)
|
130
|
+
end
|
131
|
+
|
132
|
+
def is_constant?(x, env=nil)
|
133
|
+
x.is_a?(Symbol) && is_uppercase?(x.to_s[0])
|
134
|
+
end
|
135
|
+
|
136
|
+
def is_uppercase?(char)
|
137
|
+
char == char.upcase
|
138
|
+
end
|
139
|
+
|
140
|
+
def dmatch(*args)
|
141
|
+
DMatch::match(*args)
|
142
|
+
end
|
143
|
+
|
144
|
+
def var(name)
|
145
|
+
DMatch::Var.new(name)
|
146
|
+
end
|
147
|
+
|
148
|
+
def let_var(name, pattern)
|
149
|
+
DMatch::Var.new(name) { |x, env| DMatch.new(env).match(pattern, x) }
|
150
|
+
end
|
151
|
+
|
152
|
+
def splat(name)
|
153
|
+
DMatch::Splat.new(name)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Predicated
|
4
|
+
|
5
|
+
def test(x, env=nil)
|
6
|
+
@pred == nil ? true : @pred.call(x, env)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
attr_accessor :pred
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class DMatch
|
15
|
+
|
16
|
+
class Var
|
17
|
+
include Predicated
|
18
|
+
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
def initialize(name=nil, &pred)
|
22
|
+
@name = name
|
23
|
+
self.pred = pred
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Splat < Var; end
|
28
|
+
|
29
|
+
# experimental
|
30
|
+
class FilterSplat < Splat
|
31
|
+
attr_reader :pattern
|
32
|
+
|
33
|
+
def initialize(name=nil, pattern)
|
34
|
+
super(name)
|
35
|
+
@pattern = pattern
|
36
|
+
validate_pattern
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_pattern
|
40
|
+
raise 'FilterSplat pattern cannot contain variables' if @pattern.flatten.any?{|p| p.is_a?(Var)}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# experimental
|
45
|
+
class SelectSplat < Splat
|
46
|
+
attr_reader :pattern
|
47
|
+
|
48
|
+
def initialize(name=nil, pattern)
|
49
|
+
super(name)
|
50
|
+
@pattern = pattern
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Obj
|
55
|
+
include Predicated
|
56
|
+
|
57
|
+
attr_reader :fields
|
58
|
+
|
59
|
+
def initialize(fields={}, &pred)
|
60
|
+
@fields = fields
|
61
|
+
self.pred = pred
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.of_type(klass, fields={}, &pred)
|
65
|
+
Obj.new(fields) {|x| x.is_a?(klass) && (!pred || pred.call(x))}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Pred
|
70
|
+
include Predicated
|
71
|
+
|
72
|
+
def initialize(pred_callable=nil, &pred_block)
|
73
|
+
raise 'Cannot specify both a callable and a block' if pred_callable && pred_block
|
74
|
+
self.pred = pred_callable || pred_block
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Or
|
79
|
+
attr_reader :patterns
|
80
|
+
def initialize(*patterns)
|
81
|
+
@patterns = flatten(patterns)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def flatten(ps)
|
87
|
+
ps.inject([]) {|acc, p| p.is_a?(Or) ? acc + p.patterns : acc << p}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: destructure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.12
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Winton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sourcify
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.6.0.rc4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.6.0.rc4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: binding_of_caller
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.7.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.7.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: paramix
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.1
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.0.1
|
69
|
+
description: Destructuring assignment in Ruby
|
70
|
+
email:
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- lib/destructure.rb
|
76
|
+
- lib/destructure/destructure.rb
|
77
|
+
- lib/destructure/dmatch.rb
|
78
|
+
- lib/destructure/env.rb
|
79
|
+
- lib/destructure/magic.rb
|
80
|
+
- lib/destructure/sexp_transformer.rb
|
81
|
+
- lib/destructure/types.rb
|
82
|
+
homepage: http://rubygems.org/gems/destructure
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.2.2
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Destructuring assignment in Ruby
|
106
|
+
test_files: []
|