destructure 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|