algebrick 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/README.md +6 -3
- data/README_FULL.md +13 -21
- data/VERSION +1 -1
- data/doc/actor.rb +21 -0
- data/doc/data.in.rb +99 -0
- data/doc/data.out.rb +103 -0
- data/doc/extending_behavior.in.rb +61 -0
- data/doc/extending_behavior.out.rb +62 -0
- data/doc/format.rb +75 -0
- data/doc/init.rb +1 -0
- data/doc/json.in.rb +39 -0
- data/doc/json.out.rb +43 -0
- data/doc/null.in.rb +36 -0
- data/doc/null.out.rb +40 -0
- data/doc/parametrized.in.rb +37 -0
- data/doc/parametrized.out.rb +41 -0
- data/doc/pattern_matching.in.rb +116 -0
- data/doc/pattern_matching.out.rb +122 -0
- data/doc/quick_example.in.rb +27 -0
- data/doc/quick_example.out.rb +27 -0
- data/doc/tree1.in.rb +10 -0
- data/doc/tree1.out.rb +10 -0
- data/doc/type_def.in.rb +21 -0
- data/doc/type_def.out.rb +21 -0
- data/doc/values.in.rb +52 -0
- data/doc/values.out.rb +58 -0
- data/lib/algebrick/atom.rb +49 -0
- data/lib/algebrick/dsl.rb +104 -0
- data/lib/algebrick/field_method_readers.rb +43 -0
- data/lib/algebrick/matcher_delegations.rb +45 -0
- data/lib/algebrick/matchers/abstract.rb +127 -0
- data/lib/algebrick/matchers/abstract_logic.rb +38 -0
- data/lib/algebrick/matchers/and.rb +29 -0
- data/lib/algebrick/matchers/any.rb +37 -0
- data/lib/algebrick/matchers/array.rb +57 -0
- data/lib/algebrick/matchers/atom.rb +28 -0
- data/lib/algebrick/matchers/not.rb +44 -0
- data/lib/algebrick/matchers/or.rb +51 -0
- data/lib/algebrick/matchers/product.rb +73 -0
- data/lib/algebrick/matchers/variant.rb +29 -0
- data/lib/algebrick/matchers/wrapper.rb +57 -0
- data/lib/algebrick/matchers.rb +31 -0
- data/lib/algebrick/matching.rb +62 -0
- data/lib/algebrick/parametrized_type.rb +122 -0
- data/lib/algebrick/product_constructors/abstract.rb +70 -0
- data/lib/algebrick/product_constructors/basic.rb +47 -0
- data/lib/algebrick/product_constructors/named.rb +58 -0
- data/lib/algebrick/product_constructors.rb +25 -0
- data/lib/algebrick/product_variant.rb +195 -0
- data/lib/algebrick/reclude.rb +39 -0
- data/lib/algebrick/serializer.rb +129 -0
- data/lib/algebrick/serializers.rb +25 -0
- data/lib/algebrick/type.rb +61 -0
- data/lib/algebrick/type_check.rb +58 -0
- data/lib/algebrick/types.rb +59 -0
- data/lib/algebrick/value.rb +41 -0
- data/lib/algebrick.rb +14 -1170
- data/spec/algebrick_test.rb +708 -0
- metadata +105 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df5b658188ef94835725f056d1872942a1f10fe3
|
4
|
+
data.tar.gz: 5ac342bdfaaa354e8bf9575c25f1e06105170d18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1207c3040b5d32c3d64cc47c29f9bccb83a979600cc9dc169d1424b6514297bc3c844bfa12e5d851663f08382019cdf948a85397e4363f21b4f835756c92235a
|
7
|
+
data.tar.gz: af70a94f628feb94b59995adc5d6740cafdddf655a4c560f25e788c31f758138e21a6617abff047fc7d195176cf5357b7e9a65decb25916c0176d240b0f0eeb8
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/pitr-ch/algebrick)
|
4
4
|
|
5
|
-
|
5
|
+
Typed structs on steroids based on algebraic types and pattern matching seamlessly integrating with standard Ruby features.
|
6
6
|
|
7
7
|
- Documentation: <http://blog.pitr.ch/algebrick>
|
8
8
|
- Source: <https://github.com/pitr-ch/algebrick>
|
@@ -10,8 +10,11 @@ It's a gem providing **algebraic types** and **pattern matching** seamlessly int
|
|
10
10
|
|
11
11
|
## What is it good for?
|
12
12
|
|
13
|
-
-
|
14
|
-
-
|
13
|
+
- Well defined data structures.
|
14
|
+
- Actor messages see [new Actor implementation](http://rubydoc.info/gems/concurrent-ruby/Concurrent/Actress)
|
15
|
+
in [concurrent-ruby](concurrent-ruby.com).
|
16
|
+
- Describing message protocols in message-based cross-process communication.
|
17
|
+
Algebraic types play nice with JSON de/serialization.
|
15
18
|
- and more...
|
16
19
|
|
17
20
|
## Quick example
|
data/README_FULL.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Algebrick
|
2
2
|
|
3
|
-
|
3
|
+
Typed structs on steroids based on algebraic types and pattern matching seamlessly integrating with standard Ruby features.
|
4
4
|
|
5
5
|
- Documentation: <http://blog.pitr.ch/algebrick>
|
6
6
|
- Source: <https://github.com/pitr-ch/algebrick>
|
@@ -12,19 +12,23 @@ It's a gem providing **algebraic types** and **pattern matching** and seamlessly
|
|
12
12
|
|
13
13
|
## Algebraic types
|
14
14
|
|
15
|
-
Algebraic
|
15
|
+
Algebraic types are:
|
16
|
+
|
17
|
+
- products with a given number of fields,
|
18
|
+
- or a kind of composite type, i.e. a type formed by combining other types.
|
19
|
+
|
16
20
|
In Haskell algebraic type looks like this:
|
17
21
|
|
18
22
|
data Tree = Empty
|
19
23
|
| Leaf Int
|
20
24
|
| Node Tree Tree
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
|
26
|
+
depth :: Tree -> Int
|
27
|
+
depth Empty = 0
|
28
|
+
depth (Leaf n) = 1
|
29
|
+
depth (Node l r) = 1 + max (depth l) (depth r)
|
26
30
|
|
27
|
-
|
31
|
+
depth (Node Empty (Leaf 5)) -- => 2
|
28
32
|
|
29
33
|
This snippet defines type `Tree` which has 3 possible values:
|
30
34
|
|
@@ -95,19 +99,7 @@ Algebraic types also play nice with JSON serialization and de-serialization maki
|
|
95
99
|
|
96
100
|
Just a small snippet how it can be used in Actor model world.
|
97
101
|
|
98
|
-
|
99
|
-
def initialize(executor)
|
100
|
-
super()
|
101
|
-
@executor = executor
|
102
|
-
end
|
103
|
-
|
104
|
-
def on_message(message)
|
105
|
-
match message,
|
106
|
-
Work.(~any, ~any) >-> actor, work do
|
107
|
-
@executor.tell Finished[actor, work.call, self.reference]
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
102
|
+
{include:file:doc/actor.rb}
|
111
103
|
|
112
104
|
<!--
|
113
105
|
### Null Object Pattern
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/doc/actor.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Work = Algebrick.type do
|
2
|
+
fields key: String, work: Proc
|
3
|
+
end
|
4
|
+
|
5
|
+
Finished = Algebrick.type do
|
6
|
+
fields key: String, result: Object, worker: Worker
|
7
|
+
end
|
8
|
+
|
9
|
+
class Worker < AbstractActor
|
10
|
+
def initialize(executor)
|
11
|
+
super()
|
12
|
+
@executor = executor
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_message(message)
|
16
|
+
match message,
|
17
|
+
Work.(~any, ~any) >-> key, work do
|
18
|
+
@executor.tell Finished[key, work.call, self]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/doc/data.in.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
extend Algebrick::Matching
|
2
|
+
|
3
|
+
# Simple data structures like trees
|
4
|
+
Tree = Algebrick.type do |tree|
|
5
|
+
variants Tip = type,
|
6
|
+
Node = type { fields value: Object, left: tree, right: tree }
|
7
|
+
end
|
8
|
+
|
9
|
+
module Tree
|
10
|
+
def depth
|
11
|
+
match self,
|
12
|
+
Tip.to_m >> 0,
|
13
|
+
Node.(any, ~any, ~any) >-> left, right do
|
14
|
+
1 + [left.depth, right.depth].max
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end #
|
18
|
+
|
19
|
+
tree = Node[2,
|
20
|
+
Tip,
|
21
|
+
Node[5,
|
22
|
+
Node[4, Tip, Tip],
|
23
|
+
Node[6, Tip, Tip]]]
|
24
|
+
tree.depth
|
25
|
+
|
26
|
+
# Whenever you find yourself to pass around too many fragile Hash-Array structures
|
27
|
+
# e.g. for menus.
|
28
|
+
Menu = Algebrick.type do |menu|
|
29
|
+
Item = Algebrick.type do
|
30
|
+
variants Delimiter = atom,
|
31
|
+
Link = type { fields! label: String, url: String },
|
32
|
+
Group = type { fields! label: String, submenu: menu }
|
33
|
+
end
|
34
|
+
|
35
|
+
fields! item: Item, next: menu
|
36
|
+
variants None = atom, menu
|
37
|
+
end
|
38
|
+
None
|
39
|
+
Item
|
40
|
+
|
41
|
+
module Link
|
42
|
+
def self.new(*fields)
|
43
|
+
super(*fields).tap { |menu| valid! menu.url }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.valid!(url)
|
47
|
+
# stub
|
48
|
+
end
|
49
|
+
end #
|
50
|
+
|
51
|
+
module Item
|
52
|
+
def draw_menu(indent = 0)
|
53
|
+
match self,
|
54
|
+
Delimiter >-> { [' '*indent + '-'*10] },
|
55
|
+
Link.(label: ~any) >-> label { [' '*indent + label] },
|
56
|
+
(on ~Group do |(label, sub_menu)|
|
57
|
+
[' '*indent + label] + sub_menu.draw_menu(indent + 2)
|
58
|
+
end)
|
59
|
+
end
|
60
|
+
end #
|
61
|
+
|
62
|
+
module Menu
|
63
|
+
def self.build(*items)
|
64
|
+
items.reverse_each.reduce(None) { |menu, item| Menu[item, menu] }
|
65
|
+
end
|
66
|
+
|
67
|
+
include Enumerable
|
68
|
+
|
69
|
+
def each(&block)
|
70
|
+
it = self
|
71
|
+
loop do
|
72
|
+
break if None === it
|
73
|
+
block.call it.item
|
74
|
+
it = it.next
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def draw_menu(indent = 0)
|
79
|
+
map { |item| item.draw_menu indent }.reduce(&:+)
|
80
|
+
end
|
81
|
+
end #
|
82
|
+
|
83
|
+
sub_menu = Menu.build Link['Red Hat', '#red-hat'],
|
84
|
+
Delimiter,
|
85
|
+
Link['Ubuntu', '#ubuntu'],
|
86
|
+
Link['Mint', '#mint']
|
87
|
+
|
88
|
+
menu = Menu.build Link['Home', '#home'],
|
89
|
+
Delimiter,
|
90
|
+
Group['Linux', sub_menu],
|
91
|
+
Link['About', '#about']
|
92
|
+
|
93
|
+
menu.draw_menu.join("\n")
|
94
|
+
|
95
|
+
|
96
|
+
# Group['Products',
|
97
|
+
# Menu[]],
|
98
|
+
# Link['About', '#about']
|
99
|
+
#]]
|
data/doc/data.out.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
extend Algebrick::Matching # => main
|
2
|
+
|
3
|
+
# Simple data structures like trees
|
4
|
+
Tree = Algebrick.type do |tree|
|
5
|
+
variants Tip = type,
|
6
|
+
Node = type { fields value: Object, left: tree, right: tree }
|
7
|
+
end # => Tree(Tip | Node)
|
8
|
+
|
9
|
+
module Tree
|
10
|
+
def depth
|
11
|
+
match self,
|
12
|
+
Tip.to_m >> 0,
|
13
|
+
Node.(any, ~any, ~any) >-> left, right do
|
14
|
+
1 + [left.depth, right.depth].max
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
tree = Node[2,
|
20
|
+
Tip,
|
21
|
+
Node[5,
|
22
|
+
Node[4, Tip, Tip],
|
23
|
+
Node[6, Tip, Tip]]]
|
24
|
+
# => Node[value: 2, left: Tip, right: Node[value: 5, left: Node[value: 4, left: Tip, right: Tip], right: Node[value: 6, left: Tip, right: Tip]]]
|
25
|
+
tree.depth # => 3
|
26
|
+
|
27
|
+
# Whenever you find yourself to pass around too many fragile Hash-Array structures
|
28
|
+
# e.g. for menus.
|
29
|
+
Menu = Algebrick.type do |menu|
|
30
|
+
Item = Algebrick.type do
|
31
|
+
variants Delimiter = atom,
|
32
|
+
Link = type { fields! label: String, url: String },
|
33
|
+
Group = type { fields! label: String, submenu: menu }
|
34
|
+
end
|
35
|
+
|
36
|
+
fields! item: Item, next: menu
|
37
|
+
variants None = atom, menu
|
38
|
+
end # => Menu(None | Menu(item: Item, next: Menu))
|
39
|
+
None # => None
|
40
|
+
Item # => Item(Delimiter | Link | Group)
|
41
|
+
|
42
|
+
module Link
|
43
|
+
def self.new(*fields)
|
44
|
+
super(*fields).tap { |menu| valid! menu.url }
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.valid!(url)
|
48
|
+
# stub
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module Item
|
53
|
+
def draw_menu(indent = 0)
|
54
|
+
match self,
|
55
|
+
Delimiter >-> { [' '*indent + '-'*10] },
|
56
|
+
Link.(label: ~any) >-> label { [' '*indent + label] },
|
57
|
+
(on ~Group do |(label, sub_menu)|
|
58
|
+
[' '*indent + label] + sub_menu.draw_menu(indent + 2)
|
59
|
+
end)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module Menu
|
64
|
+
def self.build(*items)
|
65
|
+
items.reverse_each.reduce(None) { |menu, item| Menu[item, menu] }
|
66
|
+
end
|
67
|
+
|
68
|
+
include Enumerable
|
69
|
+
|
70
|
+
def each(&block)
|
71
|
+
it = self
|
72
|
+
loop do
|
73
|
+
break if None === it
|
74
|
+
block.call it.item
|
75
|
+
it = it.next
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def draw_menu(indent = 0)
|
80
|
+
map { |item| item.draw_menu indent }.reduce(&:+)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
sub_menu = Menu.build Link['Red Hat', '#red-hat'],
|
85
|
+
Delimiter,
|
86
|
+
Link['Ubuntu', '#ubuntu'],
|
87
|
+
Link['Mint', '#mint']
|
88
|
+
# => Menu[item: Link[label: Red Hat, url: #red-hat], next: Menu[item: Delimiter, next: Menu[item: Link[label: Ubuntu, url: #ubuntu], next: Menu[item: Link[label: Mint, url: #mint], next: None]]]]
|
89
|
+
|
90
|
+
menu = Menu.build Link['Home', '#home'],
|
91
|
+
Delimiter,
|
92
|
+
Group['Linux', sub_menu],
|
93
|
+
Link['About', '#about']
|
94
|
+
# => Menu[item: Link[label: Home, url: #home], next: Menu[item: Delimiter, next: Menu[item: Group[label: Linux, submenu: Menu[item: Link[label: Red Hat, url: #red-hat], next: Menu[item: Delimiter, next: Menu[item: Link[label: Ubuntu, url: #ubuntu], next: Menu[item: Link[label: Mint, url: #mint], next: None]]]]], next: Menu[item: Link[label: About, url: #about], next: None]]]]
|
95
|
+
|
96
|
+
menu.draw_menu.join("\n")
|
97
|
+
# => "Home\n----------\nLinux\n Red Hat\n ----------\n Ubuntu\n Mint\nAbout"
|
98
|
+
|
99
|
+
|
100
|
+
# Group['Products',
|
101
|
+
# Menu[]],
|
102
|
+
# Link['About', '#about']
|
103
|
+
#]]
|
@@ -0,0 +1,61 @@
|
|
1
|
+
Maybe = Algebrick.type do
|
2
|
+
variants None = atom,
|
3
|
+
Some = type { fields Object }
|
4
|
+
end
|
5
|
+
|
6
|
+
# Types can be extended with usual syntax for modules and using Ruby supports module reopening.
|
7
|
+
module Maybe
|
8
|
+
def maybe(&block)
|
9
|
+
case self
|
10
|
+
when None
|
11
|
+
when Some
|
12
|
+
block.call value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end #
|
16
|
+
|
17
|
+
# #maybe method is defined on both values (None, Some) of Maybe.
|
18
|
+
None.maybe { |_| raise 'never ever happens' }
|
19
|
+
# Block is called with the value.
|
20
|
+
Some[1].maybe { |v| v*2 }
|
21
|
+
|
22
|
+
# It also works as expected when modules like Comparable are included.
|
23
|
+
Season = Algebrick.type do
|
24
|
+
variants Spring = atom,
|
25
|
+
Summer = atom,
|
26
|
+
Autumn = atom,
|
27
|
+
Winter = atom
|
28
|
+
end
|
29
|
+
|
30
|
+
module Season
|
31
|
+
include Comparable
|
32
|
+
ORDER = Season.variants.each_with_index.each_with_object({}) { |(season, i), h| h[season] = i }
|
33
|
+
|
34
|
+
def <=>(other)
|
35
|
+
Type! other, Season
|
36
|
+
ORDER[self] <=> ORDER[other]
|
37
|
+
end
|
38
|
+
end #
|
39
|
+
|
40
|
+
Quarter = Algebrick.type do
|
41
|
+
fields! year: Integer, season: Season
|
42
|
+
end
|
43
|
+
|
44
|
+
module Quarter
|
45
|
+
include Comparable
|
46
|
+
|
47
|
+
def <=>(other)
|
48
|
+
Type! other, Quarter
|
49
|
+
[year, season] <=> [other.year, other.season]
|
50
|
+
end
|
51
|
+
end #
|
52
|
+
|
53
|
+
# Now Quarters and Seasons can be compared as expected.
|
54
|
+
[Winter, Summer, Spring, Autumn].sort
|
55
|
+
Quarter[2013, Spring] < Quarter[2013, Summer]
|
56
|
+
Quarter[2014, Spring] > Quarter[2013, Summer]
|
57
|
+
Quarter[2014, Spring] == Quarter[2014, Spring]
|
58
|
+
[Quarter[2013, Spring], Quarter[2013, Summer], Quarter[2014, Spring]].sort
|
59
|
+
|
60
|
+
|
61
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
Maybe = Algebrick.type do
|
2
|
+
variants None = atom,
|
3
|
+
Some = type { fields Object }
|
4
|
+
end # => Maybe(None | Some)
|
5
|
+
|
6
|
+
# Types can be extended with usual syntax for modules and using Ruby supports module reopening.
|
7
|
+
module Maybe
|
8
|
+
def maybe(&block)
|
9
|
+
case self
|
10
|
+
when None
|
11
|
+
when Some
|
12
|
+
block.call value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# #maybe method is defined on both values (None, Some) of Maybe.
|
18
|
+
None.maybe { |_| raise 'never ever happens' } # => nil
|
19
|
+
# Block is called with the value.
|
20
|
+
Some[1].maybe { |v| v*2 } # => 2
|
21
|
+
|
22
|
+
# It also works as expected when modules like Comparable are included.
|
23
|
+
Season = Algebrick.type do
|
24
|
+
variants Spring = atom,
|
25
|
+
Summer = atom,
|
26
|
+
Autumn = atom,
|
27
|
+
Winter = atom
|
28
|
+
end # => Season(Spring | Summer | Autumn | Winter)
|
29
|
+
|
30
|
+
module Season
|
31
|
+
include Comparable
|
32
|
+
ORDER = Season.variants.each_with_index.each_with_object({}) { |(season, i), h| h[season] = i }
|
33
|
+
|
34
|
+
def <=>(other)
|
35
|
+
Type! other, Season
|
36
|
+
ORDER[self] <=> ORDER[other]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Quarter = Algebrick.type do
|
41
|
+
fields! year: Integer, season: Season
|
42
|
+
end # => Quarter(year: Integer, season: Season)
|
43
|
+
|
44
|
+
module Quarter
|
45
|
+
include Comparable
|
46
|
+
|
47
|
+
def <=>(other)
|
48
|
+
Type! other, Quarter
|
49
|
+
[year, season] <=> [other.year, other.season]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Now Quarters and Seasons can be compared as expected.
|
54
|
+
[Winter, Summer, Spring, Autumn].sort # => [Spring, Summer, Autumn, Winter]
|
55
|
+
Quarter[2013, Spring] < Quarter[2013, Summer] # => true
|
56
|
+
Quarter[2014, Spring] > Quarter[2013, Summer] # => true
|
57
|
+
Quarter[2014, Spring] == Quarter[2014, Spring] # => true
|
58
|
+
[Quarter[2013, Spring], Quarter[2013, Summer], Quarter[2014, Spring]].sort
|
59
|
+
# => [Quarter[year: 2013, season: Spring], Quarter[year: 2013, season: Summer], Quarter[year: 2014, season: Spring]]
|
60
|
+
|
61
|
+
|
62
|
+
|
data/doc/format.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'pry'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
input_paths = if ARGV.empty?
|
7
|
+
Dir.glob("#{File.dirname(__FILE__)}/*.in.rb")
|
8
|
+
else
|
9
|
+
ARGV
|
10
|
+
end.map { |p| File.expand_path p }
|
11
|
+
|
12
|
+
input_paths.each_with_index do |input_path, i|
|
13
|
+
|
14
|
+
pid = fork do
|
15
|
+
require_relative 'init.rb'
|
16
|
+
|
17
|
+
begin
|
18
|
+
output_path = input_path.gsub /\.in\.rb$/, '.out.rb'
|
19
|
+
input = File.readlines(input_path)
|
20
|
+
|
21
|
+
chunks = []
|
22
|
+
line = ''
|
23
|
+
|
24
|
+
while !input.empty?
|
25
|
+
line += input.shift
|
26
|
+
if Pry::Code.complete_expression? line
|
27
|
+
chunks << line
|
28
|
+
line = ''
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
raise unless line.empty?
|
33
|
+
|
34
|
+
chunks.map! { |chunk| [chunk, [chunk.split($/).size, 1].max] }
|
35
|
+
environment = Module.new.send :binding
|
36
|
+
evaluate = ->(code, line) do
|
37
|
+
eval(code, environment, input_path, line)
|
38
|
+
end
|
39
|
+
|
40
|
+
indent = 50
|
41
|
+
|
42
|
+
line_count = 1
|
43
|
+
output = ''
|
44
|
+
chunks.each do |chunk, lines|
|
45
|
+
result = evaluate.(chunk, line_count)
|
46
|
+
unless chunk.strip.empty? || chunk =~ /\A *#/
|
47
|
+
pre_lines = chunk.lines.to_a
|
48
|
+
last_line = pre_lines.pop
|
49
|
+
output << pre_lines.join
|
50
|
+
|
51
|
+
if last_line =~ /\#$/
|
52
|
+
output << last_line.gsub(/\#$/, '')
|
53
|
+
else
|
54
|
+
if last_line.size < indent && result.inspect.size < indent
|
55
|
+
output << "%-#{indent}s %s" % [last_line.chomp, "# => #{result.inspect}\n"]
|
56
|
+
else
|
57
|
+
output << last_line << " # => #{result.inspect}\n"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
else
|
61
|
+
output << chunk
|
62
|
+
end
|
63
|
+
line_count += lines
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "#{input_path}\n -> #{output_path}"
|
67
|
+
#puts output
|
68
|
+
File.write(output_path, output)
|
69
|
+
rescue => ex
|
70
|
+
puts "#{ex} (#{ex.class})\n#{ex.backtrace * "\n"}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Process.wait pid
|
75
|
+
end
|
data/doc/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'algebrick'
|
data/doc/json.in.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
extend Algebrick::Matching
|
2
|
+
|
3
|
+
# Lets define message-protocol for a cross-process communication.
|
4
|
+
Request = Algebrick.type do
|
5
|
+
User = type { fields login: String, password: String }
|
6
|
+
|
7
|
+
variants CreateUser = type { fields User },
|
8
|
+
GetUser = type { fields login: String }
|
9
|
+
end
|
10
|
+
|
11
|
+
Response = Algebrick.type do
|
12
|
+
variants Success = type { fields Object },
|
13
|
+
Failure = type { fields error: String }
|
14
|
+
end
|
15
|
+
|
16
|
+
Message = Algebrick.type { variants Request, Response }
|
17
|
+
|
18
|
+
require 'algebrick/serializer'
|
19
|
+
require 'multi_json'
|
20
|
+
|
21
|
+
# Prepare a message for sending.
|
22
|
+
serializer = Algebrick::Serializer.new
|
23
|
+
request = CreateUser[User['root', 'lajDh4']]
|
24
|
+
raw_request = MultiJson.dump serializer.dump(request)
|
25
|
+
|
26
|
+
# Receive the message.
|
27
|
+
response = match serializer.load(MultiJson.load(raw_request)),
|
28
|
+
(on CreateUser.(~any) do |user|
|
29
|
+
# create the user and send success
|
30
|
+
Success[user]
|
31
|
+
end)
|
32
|
+
|
33
|
+
# Send response.
|
34
|
+
response_raw = MultiJson.dump serializer.dump(response)
|
35
|
+
|
36
|
+
# Receive response.
|
37
|
+
serializer.load(MultiJson.load(response_raw))
|
38
|
+
|
39
|
+
|
data/doc/json.out.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
extend Algebrick::Matching # => main
|
2
|
+
|
3
|
+
# Lets define message-protocol for a cross-process communication.
|
4
|
+
Request = Algebrick.type do
|
5
|
+
User = type { fields login: String, password: String }
|
6
|
+
|
7
|
+
variants CreateUser = type { fields User },
|
8
|
+
GetUser = type { fields login: String }
|
9
|
+
end # => Request(CreateUser | GetUser)
|
10
|
+
|
11
|
+
Response = Algebrick.type do
|
12
|
+
variants Success = type { fields Object },
|
13
|
+
Failure = type { fields error: String }
|
14
|
+
end # => Response(Success | Failure)
|
15
|
+
|
16
|
+
Message = Algebrick.type { variants Request, Response }
|
17
|
+
# => Message(Request | Response)
|
18
|
+
|
19
|
+
require 'algebrick/serializer' # => true
|
20
|
+
require 'multi_json' # => true
|
21
|
+
|
22
|
+
# Prepare a message for sending.
|
23
|
+
serializer = Algebrick::Serializer.new # => #<Algebrick::Serializer:0x007fab42bdf6d8>
|
24
|
+
request = CreateUser[User['root', 'lajDh4']]
|
25
|
+
# => CreateUser[User[login: root, password: lajDh4]]
|
26
|
+
raw_request = MultiJson.dump serializer.dump(request)
|
27
|
+
# => "{\"algebrick_type\":\"CreateUser\",\"algebrick_fields\":[{\"algebrick_type\":\"User\",\"login\":\"root\",\"password\":\"lajDh4\"}]}"
|
28
|
+
|
29
|
+
# Receive the message.
|
30
|
+
response = match serializer.load(MultiJson.load(raw_request)),
|
31
|
+
(on CreateUser.(~any) do |user|
|
32
|
+
# create the user and send success
|
33
|
+
Success[user]
|
34
|
+
end) # => Success[User[login: root, password: lajDh4]]
|
35
|
+
|
36
|
+
# Send response.
|
37
|
+
response_raw = MultiJson.dump serializer.dump(response)
|
38
|
+
# => "{\"algebrick_type\":\"Success\",\"algebrick_fields\":[{\"algebrick_type\":\"User\",\"login\":\"root\",\"password\":\"lajDh4\"}]}"
|
39
|
+
|
40
|
+
# Receive response.
|
41
|
+
serializer.load(MultiJson.load(response_raw)) # => Success[User[login: root, password: lajDh4]]
|
42
|
+
|
43
|
+
|
data/doc/null.in.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
extend Algebrick::Matching
|
2
|
+
|
3
|
+
def deliver_email(email)
|
4
|
+
true
|
5
|
+
end #
|
6
|
+
|
7
|
+
Contact = Algebrick.type do |contact|
|
8
|
+
variants Null = atom,
|
9
|
+
contact
|
10
|
+
fields username: String, email: String
|
11
|
+
end
|
12
|
+
|
13
|
+
module Contact
|
14
|
+
def username
|
15
|
+
match self,
|
16
|
+
Null >> 'no name',
|
17
|
+
Contact >-> { self[:username] }
|
18
|
+
end
|
19
|
+
def email
|
20
|
+
match self,
|
21
|
+
Null >> 'no email',
|
22
|
+
Contact >-> { self[:email] }
|
23
|
+
end
|
24
|
+
def deliver_personalized_email
|
25
|
+
match self,
|
26
|
+
Null >> true,
|
27
|
+
Contact >-> { deliver_email(self.email) }
|
28
|
+
end
|
29
|
+
end #
|
30
|
+
|
31
|
+
peter = Contact['peter', 'example@dot.com']
|
32
|
+
john = Contact[username: 'peter', email: 'example@dot.com']
|
33
|
+
nobody = Null
|
34
|
+
|
35
|
+
[peter, john, nobody].map &:email
|
36
|
+
[peter, john, nobody].map &:deliver_personalized_email
|
data/doc/null.out.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
extend Algebrick::Matching # => main
|
2
|
+
|
3
|
+
def deliver_email(email)
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
Contact = Algebrick.type do |contact|
|
8
|
+
variants Null = atom,
|
9
|
+
contact
|
10
|
+
fields username: String, email: String
|
11
|
+
end
|
12
|
+
# => Contact(Null | Contact(username: String, email: String))
|
13
|
+
|
14
|
+
module Contact
|
15
|
+
def username
|
16
|
+
match self,
|
17
|
+
Null >> 'no name',
|
18
|
+
Contact >-> { self[:username] }
|
19
|
+
end
|
20
|
+
def email
|
21
|
+
match self,
|
22
|
+
Null >> 'no email',
|
23
|
+
Contact >-> { self[:email] }
|
24
|
+
end
|
25
|
+
def deliver_personalized_email
|
26
|
+
match self,
|
27
|
+
Null >> true,
|
28
|
+
Contact >-> { deliver_email(self.email) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
peter = Contact['peter', 'example@dot.com'] # => Contact[username: peter, email: example@dot.com]
|
33
|
+
john = Contact[username: 'peter', email: 'example@dot.com']
|
34
|
+
# => Contact[username: peter, email: example@dot.com]
|
35
|
+
nobody = Null # => Null
|
36
|
+
|
37
|
+
[peter, john, nobody].map &:email
|
38
|
+
# => ["example@dot.com", "example@dot.com", "no email"]
|
39
|
+
[peter, john, nobody].map &:deliver_personalized_email
|
40
|
+
# => [true, true, true]
|