algebrick 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/pitr-ch/algebrick.png?branch=master)](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]
|