rewrite 0.2.0 → 0.3.0

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.
@@ -10,3 +10,5 @@
10
10
  * added Try to the Prelude
11
11
  * 5 minor enhancement:
12
12
  * called_by_name
13
+ * 6 minor enhancements:
14
+ * ByExample and Unhygienic rewriting
@@ -7,6 +7,22 @@ Rakefile
7
7
  config/hoe.rb
8
8
  config/requirements.rb
9
9
  lib/rewrite.rb
10
+ lib/rewrite/by_example.rb
11
+ lib/rewrite/by_example/any_entity.rb
12
+ lib/rewrite/by_example/bind.rb
13
+ lib/rewrite/by_example/bind_sequence.rb
14
+ lib/rewrite/by_example/composition.rb
15
+ lib/rewrite/by_example/entity_matcher.rb
16
+ lib/rewrite/by_example/length_one.rb
17
+ lib/rewrite/by_example/literal_entity.rb
18
+ lib/rewrite/by_example/nil_entity.rb
19
+ lib/rewrite/by_example/object_to_matcher.rb
20
+ lib/rewrite/by_example/returning.rb
21
+ lib/rewrite/by_example/sequence.rb
22
+ lib/rewrite/by_example/sexp_entity.rb
23
+ lib/rewrite/by_example/symbol_entity.rb
24
+ lib/rewrite/by_example/unhygienic.rb
25
+ lib/rewrite/by_example/union_of_entities_sequence.rb
10
26
  lib/rewrite/def_var.rb
11
27
  lib/rewrite/evaluation_strategies.rb
12
28
  lib/rewrite/prelude.rb
@@ -26,9 +42,12 @@ tasks/deployment.rake
26
42
  tasks/environment.rake
27
43
  tasks/website.rake
28
44
  test/test_andand.rb
45
+ test/test_by_example.rb
29
46
  test/test_call_by_name.rb
30
47
  test/test_call_by_thunk.rb
48
+ test/test_call_splatted_by_name.rb
31
49
  test/test_helper.rb
50
+ test/test_object_to_sequence_and_matcher.rb
32
51
  test/test_rewriter_helpers.rb
33
52
  test/test_syntax_let.rb
34
53
  test/test_with.rb
@@ -0,0 +1,39 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ Dir["#{File.dirname(__FILE__)}/by_example/*.rb"].each do |element|
5
+ require File.expand_path(element)
6
+ end
7
+
8
+ module Rewrite
9
+
10
+ module ByExample
11
+ module ClassMethods
12
+ # Sigh, I know that 'clever' variable names are not helpful, but I *need* to remind
13
+ # myself why I am doing this: Using a ByExample to extract what we want from a sexp
14
+ # is eating my own dog food.
15
+ DOGFOODER = ObjectToMatcher.from_object(
16
+ s(:proc,
17
+ nil,
18
+ Bind.new(:body, AnyEntity.new)
19
+ )
20
+ )
21
+
22
+ def from(&proc)
23
+ body = DOGFOODER.unfold(proc.to_sexp) or raise "Don't know how to handle #{proc.to_ruby}"
24
+ SexpEntity.from(body)
25
+ end
26
+
27
+ end
28
+
29
+ module InstanceMethods
30
+
31
+ end
32
+
33
+ def self.included(receiver)
34
+ receiver.extend ClassMethods
35
+ receiver.send :include, InstanceMethods
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,32 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require File.expand_path(File.dirname(__FILE__) +'/entity_matcher.rb')
4
+
5
+ module Rewrite
6
+
7
+ module ByExample
8
+
9
+ # Matches any entity and optionally binds that entity's value to a name in the
10
+ # unfolded result. AnyEntity.new('name') binds whatever it matches to
11
+ # 'name'.
12
+ #
13
+ # Not isomorphic to BindSequence: if you want to match a sequence
14
+ # consisting of exactly one entity and bind that to 'name', you need
15
+ # to combine AnyEntity with LengthOne:
16
+ #
17
+ # LengthOne.new(AnyEntity.new('name))
18
+ class AnyEntity < EntityMatcher
19
+
20
+ def unfold (sexp)
21
+ {}
22
+ end
23
+
24
+ def to_s
25
+ '__'
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,41 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require File.expand_path(File.dirname(__FILE__) +'/entity_matcher.rb')
4
+
5
+ module Rewrite
6
+
7
+ module ByExample
8
+
9
+ # Abstract class that matches any entity and optionally binds that entity's
10
+ # value to a name in the unfolded result. Useful for creating classes that bind
11
+ # and match simultaneously
12
+ class Bind < EntityMatcher
13
+
14
+ attr_accessor :name, :entity_matcher
15
+
16
+ def initialize(name, entity_matcher)
17
+ self.name = name.to_sym
18
+ self.entity_matcher = entity_matcher
19
+ end
20
+
21
+ def unfold (sexp)
22
+ { self.name => sexp } if entity_matcher.unfold(sexp)
23
+ end
24
+
25
+ def fold (enum_of_bindings)
26
+ enum_of_bindings[self.name]
27
+ end
28
+
29
+ def to_s
30
+ if name
31
+ "#{entity_matcher.to_s} => #{name.inspect}"
32
+ else
33
+ '*'
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,46 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require File.expand_path(File.dirname(__FILE__) +'/sequence.rb')
4
+
5
+ module Rewrite
6
+
7
+ module ByExample
8
+
9
+ # Matches any sequence of zero or more entities and binds the list
10
+ # to a name in the unfolded result. BindSequence.new('name') binds
11
+ # whatever sequence of entities it matches to 'name'.
12
+ #
13
+ # Not isomorphic to AnyEntity: AnyEntity matches one entity,
14
+ # BindSequence matches a sequence of entities. If you want to match
15
+ # a single entity that happens to consist
16
+ #
17
+ # LengthOne.new(Bind.new('name), AnyEntity.new)
18
+ class BindSequence < Sequence
19
+ attr_reader :unfolder_lambda, :name
20
+
21
+ def initialize(name)
22
+ @name = name.to_sym
23
+ @unfolder_lambda = lambda { |arr| { @name => s(*arr) } }
24
+ end
25
+
26
+ def unfolders_by_length(length)
27
+ [ unfolder_lambda ]
28
+ end
29
+
30
+ def length_range
31
+ 0..10000
32
+ end
33
+
34
+ def fold(enum_of_bindings)
35
+ enum_of_bindings[name]
36
+ end
37
+
38
+ def to_s
39
+ "__* => #{name.inspect}"
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,104 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Rewrite
4
+
5
+ module ByExample
6
+
7
+ # A Composition is an aggregation of Sequence. So where a LengthOne and
8
+ # a BindSequence are both leaves of a sequence tree, a Composition is a
9
+ # node.
10
+ class Composition < Sequence
11
+ attr_reader :sub_sequences, :length_range
12
+
13
+ def initialize(*sub_sequences)
14
+ @sub_sequences = sub_sequences#.map { |sub| Sequence.from_object(sub) }
15
+ @length_range = range_sum(@sub_sequences)
16
+ end
17
+
18
+ def unfolders_by_length(length)
19
+ unfolders_by_length_helper(sub_sequences(), length)
20
+ end
21
+
22
+ def to_s
23
+ sub_sequences.map { |ss| ss.to_s }.join(', ')
24
+ end
25
+
26
+ def fold(enum_of_bindings)
27
+ sub_sequences.inject(s()) { |refolded, sub_sequence|
28
+ folded = sub_sequence.fold(enum_of_bindings)
29
+ if folded.nil?
30
+ s(*(refolded + [nil]))
31
+ elsif sub_sequence.is_a? LengthOne
32
+ s(*(refolded + [folded]))
33
+ else
34
+ s(*(refolded + folded))
35
+ end
36
+ }
37
+ end
38
+
39
+ private
40
+
41
+ def merge_results(result1, result2)
42
+ if result1 && result2
43
+ result = result1.dup
44
+ result2.each do |key, value|
45
+ if result.include?(key)
46
+ return nil unless result[key].eql?(value)
47
+ else
48
+ result[key] = value
49
+ end
50
+ end
51
+ result
52
+ end
53
+ end
54
+
55
+ def unfolders_by_length_helper(subs, length)
56
+ range_of_lengths = range_sum(subs)
57
+ if !(range_of_lengths === length)
58
+ []
59
+ elsif subs.empty? # implied by the test above # TODO: short blog post about this
60
+ []
61
+ elsif subs.length == 1
62
+ subs.first.unfolders_by_length(length)
63
+ else
64
+ head_subsequence = subs.first
65
+ tail_subsequences = subs[1..-1]
66
+ range_of_tail_lengths = range_sum(tail_subsequences)
67
+ required_head_lengths = (length - range_of_tail_lengths.end)..(length - range_of_tail_lengths.begin)
68
+ head_lengths_to_try = range_intersection(head_subsequence.length_range, required_head_lengths)
69
+ return [] if head_lengths_to_try.begin > head_lengths_to_try.end
70
+ head_lengths_to_try.inject([]) { |accumulated_unfolders_for_entire_composition, length_of_head_subsequence_to_try|
71
+ head_unfolders = head_subsequence.unfolders_by_length(length_of_head_subsequence_to_try)
72
+ tail_unfolders = unfolders_by_length_helper(tail_subsequences, length - length_of_head_subsequence_to_try)
73
+ accumulated_unfolders_for_entire_composition + head_unfolders.inject([]) { |accumulated_unfolders_for_entire_composition2, head_unfolder|
74
+ accumulated_unfolders_for_entire_composition2 + tail_unfolders.map { |tail_unfolder|
75
+ lambda { |arr|
76
+ head_portion, tail_portion = arr[0,length_of_head_subsequence_to_try], arr[length_of_head_subsequence_to_try..-1]
77
+ merge_results(
78
+ head_unfolder.call(head_portion),
79
+ tail_unfolder.call(tail_portion)
80
+ )
81
+ }
82
+ }
83
+ }
84
+ }
85
+ end
86
+ end
87
+
88
+ def range_intersection(range1, range2)
89
+ ([range1.begin, range2.begin].max)..([range1.end, range2.end].min)
90
+ end
91
+
92
+ def range_sum(enumerable_of_sequences)
93
+ enumerable_of_sequences.inject(0..0) { |total_range, each_seq|
94
+ each_range = each_seq.length_range
95
+ (total_range.begin + each_range.begin)..(total_range.end + each_range.end)
96
+ }
97
+ end
98
+
99
+
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,48 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Rewrite
4
+
5
+ module ByExample
6
+
7
+ # Matches a single something against a sexp
8
+ # such as an array or a literal symbol
9
+ class EntityMatcher
10
+
11
+ attr_accessor :predicate
12
+
13
+ def such_that!(&predicate_clause)
14
+ self.predicate = predicate_clause
15
+ self
16
+ end
17
+
18
+ # takes a sexp and returns nil or an enumeration of
19
+ # bindings, typically as a hash. Note that the empty hash
20
+ # is truthy, and this is what we want: it means a match
21
+ # that doesn't perform any bindings
22
+ #
23
+ # Note the identity: if x.fold(enum_of_bindings) is not nil,
24
+ # then enum_of_bindings == x.unfold(x.fold(enum_of_bindings))
25
+ def unfold(sexp)
26
+ raise 'implemented by includer'
27
+ end
28
+ remove_method :unfold
29
+
30
+ # takes an enumeration of bindings and returns nil or a sexp.
31
+ #
32
+ # Note the identity: if x.unfold(sexp) is not nil, then
33
+ # sexp == x.fold(x.unfold(sexp))
34
+ def fold(enum_of_bindings)
35
+ raise 'implemented by includer'
36
+ end
37
+ remove_method :fold
38
+
39
+ # advice for unfold
40
+ def after_unfold
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,50 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Rewrite
4
+
5
+ module ByExample
6
+
7
+ # Natches a sequence containing one item. Wraps an entity matcher,
8
+ # with the wrapped matcher matching the one item.
9
+ #
10
+ # Confusing? Consider SymbolEntity.new(:foo). It matches a single
11
+ # entity, :foo. Whereas LengthOne.new(SymbolEntity.new(:foo)) matches
12
+ # a sequence of length one where that one thing happens to match :foo.
13
+ #
14
+ # This distinction is important because sequences can only consist of
15
+ # collections of other sequences. So the LengthOnes are leaves of
16
+ # the sequence trees.
17
+ class LengthOne < Sequence
18
+
19
+ attr_reader :matcher
20
+
21
+ def initialize(entity_matcher)
22
+ @matcher = entity_matcher
23
+ @lambda = lambda { |arr| entity_matcher.unfold(arr.first) }
24
+ end
25
+
26
+ def unfolders_by_length(length)
27
+ if length == 1
28
+ [ @lambda ]
29
+ else
30
+ []
31
+ end
32
+ end
33
+
34
+ def length_range
35
+ 1..1
36
+ end
37
+
38
+ def to_s
39
+ "<#{matcher.to_s}>"
40
+ end
41
+
42
+ def fold(enum_of_bindings)
43
+ matcher.fold(enum_of_bindings)
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,25 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require File.expand_path(File.dirname(__FILE__) +'/any_entity.rb')
4
+ require File.expand_path(File.dirname(__FILE__) +'/bind.rb')
5
+ require File.expand_path(File.dirname(__FILE__) +'/sexp_entity.rb')
6
+
7
+ module Rewrite
8
+
9
+ module ByExample
10
+
11
+ class LiteralEntity < SexpEntity
12
+
13
+ def initialize(name = Rewrite.gensym)
14
+ super()
15
+ self.sequence = Composition.new(
16
+ LengthOne.new(ObjectToMatcher.from_object(:lit)),
17
+ LengthOne.new(Bind.new(name, AnyEntity.new))
18
+ )
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,26 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Rewrite
4
+
5
+ module ByExample
6
+
7
+ # Matches nil.
8
+ class NilEntity < EntityMatcher
9
+
10
+ def unfold (sexp)
11
+ {} if sexp.nil?
12
+ end
13
+
14
+ def fold (enum_of_bindings)
15
+ nil
16
+ end
17
+
18
+ def to_s
19
+ "nil"
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end