swarm 0.1.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/swarm/engine/base/job.rb +31 -0
- data/lib/swarm/engine/base/queue.rb +60 -0
- data/lib/swarm/engine/volatile/job.rb +57 -0
- data/lib/swarm/engine/volatile/queue.rb +85 -0
- data/lib/swarm/engine/worker/command.rb +61 -0
- data/lib/swarm/engine/worker.rb +73 -0
- data/lib/swarm/evaluation/expression_evaluator.rb +40 -0
- data/lib/swarm/evaluation/workitem_context.rb +17 -0
- data/lib/swarm/expression.rb +107 -0
- data/lib/swarm/expressions/activity_expression.rb +11 -0
- data/lib/swarm/expressions/branch_expression.rb +44 -0
- data/lib/swarm/expressions/concurrence_expression.rb +41 -0
- data/lib/swarm/expressions/conditional_expression.rb +36 -0
- data/lib/swarm/expressions/sequence_expression.rb +16 -0
- data/lib/swarm/expressions/subprocess_expression.rb +14 -0
- data/lib/swarm/hive.rb +69 -0
- data/lib/swarm/hive_dweller.rb +170 -0
- data/lib/swarm/observers/base.rb +17 -0
- data/lib/swarm/participant.rb +18 -0
- data/lib/swarm/participants/storage_participant.rb +12 -0
- data/lib/swarm/participants/trace_participant.rb +27 -0
- data/lib/swarm/pollen/parser.rb +95 -0
- data/lib/swarm/pollen/reader.rb +22 -0
- data/lib/swarm/pollen/transformer.rb +66 -0
- data/lib/swarm/process.rb +57 -0
- data/lib/swarm/process_definition.rb +53 -0
- data/lib/swarm/router.rb +18 -0
- data/lib/swarm/storage.rb +56 -0
- data/lib/swarm/stored_workitem.rb +15 -0
- data/lib/swarm/support.rb +81 -0
- data/lib/swarm/version.rb +3 -0
- data/lib/swarm.rb +24 -0
- data/swarm.gemspec +31 -0
- metadata +199 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative "../router"
|
2
|
+
|
3
|
+
module Swarm
|
4
|
+
class BranchExpression < Expression
|
5
|
+
class InvalidPositionError < StandardError; end;
|
6
|
+
|
7
|
+
def children
|
8
|
+
(child_ids || []).map { |child_id|
|
9
|
+
Expression.fetch(child_id, hive: hive)
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def kick_off_children(at_positions)
|
14
|
+
at_positions.each do |at_position|
|
15
|
+
add_and_apply_child(at_position)
|
16
|
+
end
|
17
|
+
save
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_and_apply_child(at_position)
|
21
|
+
new_child = add_child(at_position)
|
22
|
+
new_child.apply
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_child(at_position)
|
26
|
+
node = tree[at_position]
|
27
|
+
raise InvalidPositionError unless node
|
28
|
+
expression = create_child_expression(node: node, at_position: at_position)
|
29
|
+
(self.child_ids ||= []) << expression.id
|
30
|
+
expression
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_child_expression(node:, at_position:)
|
34
|
+
klass = Router.expression_class_for_node(node)
|
35
|
+
expression = klass.create(
|
36
|
+
:hive => hive,
|
37
|
+
:parent_id => id,
|
38
|
+
:position => position + [at_position],
|
39
|
+
:workitem => workitem,
|
40
|
+
:process_id => process_id
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative "branch_expression"
|
2
|
+
|
3
|
+
module Swarm
|
4
|
+
class ConcurrenceExpression < BranchExpression
|
5
|
+
def work
|
6
|
+
kick_off_children(tree.each_index.to_a)
|
7
|
+
end
|
8
|
+
|
9
|
+
def replied_children
|
10
|
+
children.select(&:replied_at)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ready_to_proceed?
|
14
|
+
required_replies = arguments.fetch("required_replies", nil)
|
15
|
+
return all_children_replied? unless required_replies
|
16
|
+
replied_children.count >= required_replies
|
17
|
+
end
|
18
|
+
|
19
|
+
def all_children_replied?
|
20
|
+
replied_children.count == tree.size
|
21
|
+
end
|
22
|
+
|
23
|
+
def move_on_from(child)
|
24
|
+
merge_child_workitem(child)
|
25
|
+
save
|
26
|
+
if all_children_replied?
|
27
|
+
reply
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def merge_child_workitem(child)
|
32
|
+
self.workitem = Swarm::Support.deep_merge(
|
33
|
+
workitem, child.workitem, :combine_arrays => array_combination_method
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def array_combination_method
|
38
|
+
arguments.fetch("combine_arrays", "uniq")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative "branch_expression"
|
2
|
+
|
3
|
+
module Swarm
|
4
|
+
class ConditionalExpression < BranchExpression
|
5
|
+
alias_method :original_tree, :tree
|
6
|
+
|
7
|
+
def work
|
8
|
+
if tree.empty?
|
9
|
+
reply
|
10
|
+
else
|
11
|
+
kick_off_children([0])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def move_on_from(child)
|
16
|
+
self.workitem = child.workitem
|
17
|
+
reply
|
18
|
+
end
|
19
|
+
|
20
|
+
def tree
|
21
|
+
@tree ||= select_branch || []
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_branch
|
25
|
+
if branch_condition_met?
|
26
|
+
original_tree["true"]
|
27
|
+
else
|
28
|
+
original_tree["false"]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def branch_condition_met?
|
33
|
+
evaluator.check_condition(command, arguments["condition"])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative "branch_expression"
|
2
|
+
|
3
|
+
module Swarm
|
4
|
+
class SequenceExpression < BranchExpression
|
5
|
+
def work
|
6
|
+
kick_off_children([0])
|
7
|
+
end
|
8
|
+
|
9
|
+
def move_on_from(child)
|
10
|
+
self.workitem = child.workitem
|
11
|
+
kick_off_children([child.branch_position + 1])
|
12
|
+
rescue InvalidPositionError => e
|
13
|
+
reply
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Swarm
|
2
|
+
class SubprocessExpression < Expression
|
3
|
+
def work
|
4
|
+
definition = ProcessDefinition.find_by_name(arguments.fetch("name", nil))
|
5
|
+
raise Swarm::ProcessDefinition::RecordNotFoundError unless definition
|
6
|
+
process = definition.launch_process(workitem: workitem, parent_expression_id: id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def move_on_from(process)
|
10
|
+
self.workitem = process.workitem
|
11
|
+
reply
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/swarm/hive.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative "storage"
|
2
|
+
|
3
|
+
module Swarm
|
4
|
+
class Hive
|
5
|
+
class MissingTypeError < StandardError; end
|
6
|
+
class IllegalDefaultError < StandardError; end
|
7
|
+
class NoDefaultSetError < StandardError; end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def default=(default)
|
11
|
+
unless default.is_a?(self)
|
12
|
+
raise IllegalDefaultError.new("Default must be a Swarm::Hive")
|
13
|
+
end
|
14
|
+
@default = default
|
15
|
+
end
|
16
|
+
|
17
|
+
def default
|
18
|
+
unless @default
|
19
|
+
raise NoDefaultSetError.new("No default Hive defined yet")
|
20
|
+
end
|
21
|
+
@default
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :storage, :work_queue
|
26
|
+
|
27
|
+
def initialize(storage:, work_queue:)
|
28
|
+
@storage = storage
|
29
|
+
@work_queue = work_queue
|
30
|
+
end
|
31
|
+
|
32
|
+
def registered_observers
|
33
|
+
@registered_observers ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
"#<Swarm::Hive storage: #{storage.backend.class}, work_queue: #{work_queue.name}>"
|
38
|
+
end
|
39
|
+
|
40
|
+
def traced
|
41
|
+
storage["trace"] ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
def trace(new_element)
|
45
|
+
storage["trace"] = traced + [new_element]
|
46
|
+
end
|
47
|
+
|
48
|
+
def queue(action, object)
|
49
|
+
@work_queue.add_job({
|
50
|
+
:action => action,
|
51
|
+
:metadata => object.to_hash
|
52
|
+
})
|
53
|
+
end
|
54
|
+
|
55
|
+
def fetch(klass, id)
|
56
|
+
Swarm::Support.constantize(klass).fetch(id, hive: self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def reify_from_hash(hsh)
|
60
|
+
Support.symbolize_keys!(hsh)
|
61
|
+
raise MissingTypeError.new(hsh.inspect) unless hsh[:type]
|
62
|
+
Swarm::Support.constantize(hsh.delete(:type)).new_from_storage(
|
63
|
+
hsh.merge(
|
64
|
+
:hive => self
|
65
|
+
)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module Swarm
|
2
|
+
class HiveDweller
|
3
|
+
class RecordNotFoundError < StandardError; end
|
4
|
+
|
5
|
+
attr_reader :hive, :id
|
6
|
+
|
7
|
+
def initialize(hive: Hive.default, **args)
|
8
|
+
@hive = hive
|
9
|
+
@changed_attributes = {}
|
10
|
+
set_attributes(args, record_changes: false)
|
11
|
+
end
|
12
|
+
|
13
|
+
def new?
|
14
|
+
id.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def changed?
|
18
|
+
!@changed_attributes.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_attributes(args, record_changes: true)
|
22
|
+
unknown_arguments = args.keys - self.class.columns
|
23
|
+
unless unknown_arguments.empty?
|
24
|
+
raise ArgumentError, "unknown keywords: #{unknown_arguments.join(', ')}"
|
25
|
+
end
|
26
|
+
args.each do |key, value|
|
27
|
+
change_attribute(key, value, record: record_changes)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def change_attribute(key, value, record: true)
|
32
|
+
if record
|
33
|
+
@changed_attributes[key] = [send(key), value]
|
34
|
+
end
|
35
|
+
instance_variable_set(:"@#{key}", value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
other.is_a?(self.class) && other.to_hash == to_hash
|
40
|
+
end
|
41
|
+
|
42
|
+
def storage_id
|
43
|
+
self.class.storage_id_for_key(id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def storage
|
47
|
+
@hive.storage
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete
|
51
|
+
storage.delete(storage_id)
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def save
|
56
|
+
if new? || changed?
|
57
|
+
@id ||= Swarm::Support.uuid_with_timestamp
|
58
|
+
storage[storage_id] = to_hash
|
59
|
+
end
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def attributes
|
64
|
+
self.class.columns.each_with_object({}) { |col_name, hsh|
|
65
|
+
hsh[col_name.to_sym] = send(:"#{col_name}")
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_hash
|
70
|
+
hsh = {
|
71
|
+
:id => id,
|
72
|
+
:type => self.class.name
|
73
|
+
}
|
74
|
+
hsh.merge(attributes)
|
75
|
+
end
|
76
|
+
|
77
|
+
def reload!
|
78
|
+
hsh = hive.storage[storage_id]
|
79
|
+
self.class.columns.each do |column|
|
80
|
+
instance_variable_set(:"@#{column}", hsh[column.to_s])
|
81
|
+
end
|
82
|
+
self.class.associations.each do |type|
|
83
|
+
instance_variable_set(:"@#{type}", nil)
|
84
|
+
end
|
85
|
+
@changed_attributes = {}
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
class << self
|
90
|
+
include Enumerable
|
91
|
+
|
92
|
+
attr_reader :columns, :associations
|
93
|
+
|
94
|
+
def inherited(subclass)
|
95
|
+
super
|
96
|
+
subclass.instance_variable_set(:@columns, [])
|
97
|
+
subclass.instance_variable_set(:@associations, [])
|
98
|
+
end
|
99
|
+
|
100
|
+
def set_columns(*args)
|
101
|
+
attr_reader *args
|
102
|
+
args.each do |arg|
|
103
|
+
define_method("#{arg}=") { |value|
|
104
|
+
change_attribute(arg, value)
|
105
|
+
}
|
106
|
+
end
|
107
|
+
@columns = @columns | args
|
108
|
+
end
|
109
|
+
|
110
|
+
def many_to_one(type, class_name: nil)
|
111
|
+
define_method(type) do
|
112
|
+
memo = instance_variable_get(:"@#{type}")
|
113
|
+
memo || begin
|
114
|
+
key = self.send(:"#{type}_id")
|
115
|
+
return nil unless key
|
116
|
+
klass = Swarm::Support.constantize("#{class_name || type}")
|
117
|
+
instance_variable_set(:"@#{type}", klass.fetch(key, :hive => hive))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
@associations << type
|
121
|
+
end
|
122
|
+
|
123
|
+
def create(hive: Hive.default, **args)
|
124
|
+
new(hive: hive, **args).save
|
125
|
+
end
|
126
|
+
|
127
|
+
def storage_type
|
128
|
+
name.split("::").last
|
129
|
+
end
|
130
|
+
|
131
|
+
def storage_id_for_key(key)
|
132
|
+
if key.match(/^#{storage_type}\:/)
|
133
|
+
key
|
134
|
+
else
|
135
|
+
"#{storage_type}:#{key}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def new_from_storage(**args)
|
140
|
+
id = args.delete(:id)
|
141
|
+
new(**args).tap { |instance|
|
142
|
+
instance.instance_variable_set(:@id, id)
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
def fetch(key, hive: Hive.default)
|
147
|
+
hsh = hive.storage[storage_id_for_key(key)].dup
|
148
|
+
hive.reify_from_hash(hsh)
|
149
|
+
end
|
150
|
+
|
151
|
+
def ids(hive: Hive.default)
|
152
|
+
hive.storage.ids_for_type(storage_type)
|
153
|
+
end
|
154
|
+
|
155
|
+
def each(hive: Hive.default, subtypes: true, &block)
|
156
|
+
return to_enum(__method__, hive: hive, subtypes: subtypes) unless block_given?
|
157
|
+
ids(hive: hive).each do |id|
|
158
|
+
object = fetch(id, hive: hive)
|
159
|
+
if (subtypes && object.is_a?(self)) || object.class == self
|
160
|
+
yield object
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def all(hive: Hive.default, subtypes: true)
|
166
|
+
to_a(hive: hive, subtypes: subtypes)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Swarm
|
2
|
+
module Observers
|
3
|
+
class Base
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :command, :action, :metadata, :object
|
7
|
+
attr_reader :command
|
8
|
+
|
9
|
+
def initialize(command)
|
10
|
+
@command = command
|
11
|
+
end
|
12
|
+
|
13
|
+
def before_action; end
|
14
|
+
def after_action; end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Swarm
|
2
|
+
class Participant
|
3
|
+
attr_reader :hive, :expression
|
4
|
+
|
5
|
+
def initialize(hive: Hive.default, expression:)
|
6
|
+
@hive = hive
|
7
|
+
@expression = expression
|
8
|
+
end
|
9
|
+
|
10
|
+
def workitem
|
11
|
+
expression.workitem
|
12
|
+
end
|
13
|
+
|
14
|
+
def arguments
|
15
|
+
expression.arguments
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "../participant"
|
2
|
+
|
3
|
+
module Swarm
|
4
|
+
class TraceParticipant < Participant
|
5
|
+
def work
|
6
|
+
if text
|
7
|
+
append_to_workitem_trace
|
8
|
+
append_to_hive_trace
|
9
|
+
end
|
10
|
+
expression.reply
|
11
|
+
end
|
12
|
+
|
13
|
+
def text
|
14
|
+
@text ||= arguments.fetch("text", nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
def append_to_workitem_trace
|
18
|
+
traced = workitem["traced"] || []
|
19
|
+
traced << text
|
20
|
+
expression.workitem = workitem.merge("traced" => traced)
|
21
|
+
end
|
22
|
+
|
23
|
+
def append_to_hive_trace
|
24
|
+
hive.trace(text)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "parslet"
|
2
|
+
|
3
|
+
module Swarm
|
4
|
+
module Pollen
|
5
|
+
class Parser < Parslet::Parser
|
6
|
+
def optionally_spaced(atom)
|
7
|
+
spaces? >> atom >> spaces?
|
8
|
+
end
|
9
|
+
|
10
|
+
rule(:eol) { (optionally_spaced(match['\n'])).repeat(1) }
|
11
|
+
rule(:whitespace) { match('\s').repeat(0) }
|
12
|
+
rule(:spaces) { match[' \t'].repeat(1) }
|
13
|
+
rule(:spaces?) { spaces.maybe }
|
14
|
+
rule(:comma) { optionally_spaced(str(',')) }
|
15
|
+
|
16
|
+
rule(:integer) {
|
17
|
+
match['0-9'].repeat(1).as(:integer)
|
18
|
+
}
|
19
|
+
|
20
|
+
rule(:float) {
|
21
|
+
(match['0-9'].repeat(1) >> str('.') >> match['0-9'].repeat(1)).as(:float)
|
22
|
+
}
|
23
|
+
|
24
|
+
rule(:line) {
|
25
|
+
(match['\n'].absent? >> any).repeat(1).as(:line)
|
26
|
+
}
|
27
|
+
|
28
|
+
rule(:string) {
|
29
|
+
(str("'") | str('"')).capture(:q) >>
|
30
|
+
(str('\\') >> any |
|
31
|
+
dynamic { |s,c| str(c.captures[:q]) }.absent? >> any
|
32
|
+
).repeat.as(:string) >> dynamic { |s,c| str(c.captures[:q]) }
|
33
|
+
}
|
34
|
+
|
35
|
+
rule(:colon_pair) {
|
36
|
+
token.as(:key) >> str(':') >> spaces? >> string.as(:value)
|
37
|
+
}
|
38
|
+
|
39
|
+
rule(:symbol) { str(':') >> token.as(:symbol) }
|
40
|
+
rule(:token) { (match('[a-z_]') >> match('[a-zA-Z0-9_]').repeat(0)).as(:token) }
|
41
|
+
|
42
|
+
rule(:rocket_pair) {
|
43
|
+
(symbol | string).as(:key) >> optionally_spaced(str('=>')) >> string.as(:value)
|
44
|
+
}
|
45
|
+
|
46
|
+
rule(:key_value_pair) { rocket_pair | colon_pair }
|
47
|
+
|
48
|
+
rule(:key_value_list) {
|
49
|
+
key_value_pair >> (comma >> key_value_pair).repeat(0)
|
50
|
+
}
|
51
|
+
|
52
|
+
rule(:arguments) {
|
53
|
+
key_value_list.as(:arguments) | string.as(:text_argument)
|
54
|
+
}
|
55
|
+
|
56
|
+
rule(:reserved_word) {
|
57
|
+
%w(if unless else end).map { |w| str(w) }.reduce(:|)
|
58
|
+
}
|
59
|
+
|
60
|
+
rule(:expression) {
|
61
|
+
reserved_word.absent? >> token.as(:command) >> (spaces >> arguments).maybe
|
62
|
+
}
|
63
|
+
|
64
|
+
rule(:tree) {
|
65
|
+
((conditional_block | branch_block | expression) >> eol).repeat(0)
|
66
|
+
}
|
67
|
+
|
68
|
+
rule(:conditional_block) {
|
69
|
+
(str('if') | str('unless')).as(:conditional) >>
|
70
|
+
spaces >> string.as(:conditional_clause) >> eol >>
|
71
|
+
tree.as(:true_tree) >>
|
72
|
+
(str('else') >> eol >> tree.as(:false_tree)).maybe >>
|
73
|
+
str('end')
|
74
|
+
}
|
75
|
+
|
76
|
+
rule(:branch_block) {
|
77
|
+
expression >> spaces >> str('do') >> eol >>
|
78
|
+
tree.as(:tree) >>
|
79
|
+
str('end')
|
80
|
+
}
|
81
|
+
|
82
|
+
rule(:metadata_entry) {
|
83
|
+
token.as(:key) >> str(':') >> spaces? >>
|
84
|
+
(string | float | integer | line).as(:value)
|
85
|
+
}
|
86
|
+
|
87
|
+
rule(:metadata) {
|
88
|
+
str('---') >> eol >> (metadata_entry >> eol).repeat(0) >> str('---') >> eol
|
89
|
+
}
|
90
|
+
|
91
|
+
rule(:document) { whitespace >> metadata.maybe.as(:metadata) >> branch_block.as(:tree) >> whitespace }
|
92
|
+
root(:document)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative "parser"
|
2
|
+
require_relative "transformer"
|
3
|
+
|
4
|
+
module Swarm
|
5
|
+
module Pollen
|
6
|
+
class Reader
|
7
|
+
def initialize(pollen)
|
8
|
+
@pollen = pollen
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
Transformer.new.apply(
|
13
|
+
Parser.new.parse(@pollen, :reporter => Parslet::ErrorReporter::Deepest.new)
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_json
|
18
|
+
to_hash.to_json
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "parslet"
|
2
|
+
|
3
|
+
module Swarm
|
4
|
+
module Pollen
|
5
|
+
class Transformer < Parslet::Transform
|
6
|
+
class << self
|
7
|
+
def transform_arguments(args)
|
8
|
+
if args.is_a?(Array) && !args.empty?
|
9
|
+
args.reduce(:merge)
|
10
|
+
else
|
11
|
+
args
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
rule(:symbol => simple(:sym)) { sym.to_s }
|
17
|
+
rule(:token => simple(:token)) { token.to_s }
|
18
|
+
rule(:string => simple(:st)) { st.to_s }
|
19
|
+
rule(:line => simple(:line)) { line.to_s }
|
20
|
+
rule(:float => simple(:float)) { float.to_f }
|
21
|
+
rule(:integer => simple(:int)) { int.to_i }
|
22
|
+
|
23
|
+
rule(:key => simple(:key), :value => simple(:value)) {
|
24
|
+
{ key => value }
|
25
|
+
}
|
26
|
+
rule(:conditional => simple(:conditional), :conditional_clause => simple(:clause), :true_tree => subtree(:true_tree), :false_tree => subtree(:false_tree)) {
|
27
|
+
[conditional.to_s, { "condition" => clause }, {
|
28
|
+
"true" => [
|
29
|
+
["sequence", {}, true_tree]
|
30
|
+
],
|
31
|
+
"false" => [
|
32
|
+
["sequence", {}, false_tree]
|
33
|
+
],
|
34
|
+
}]
|
35
|
+
}
|
36
|
+
|
37
|
+
rule(:conditional => simple(:conditional), :conditional_clause => simple(:clause), :true_tree => subtree(:true_tree)) {
|
38
|
+
[conditional.to_s, { "condition" => clause }, {
|
39
|
+
"true" => [
|
40
|
+
["sequence", {}, true_tree]
|
41
|
+
]
|
42
|
+
}]
|
43
|
+
}
|
44
|
+
|
45
|
+
rule(:command => simple(:command)) {
|
46
|
+
[command.to_s, {}, []]
|
47
|
+
}
|
48
|
+
rule(:command => simple(:command), :tree => subtree(:tree)) {
|
49
|
+
[command.to_s, {}, tree]
|
50
|
+
}
|
51
|
+
rule(:command => simple(:command), :arguments => subtree(:args)) { |captures|
|
52
|
+
[captures[:command].to_s, transform_arguments(captures[:args]), []]
|
53
|
+
}
|
54
|
+
rule(:command => simple(:command), :arguments => subtree(:args), :tree => subtree(:tree)) { |captures|
|
55
|
+
[captures[:command].to_s, transform_arguments(captures[:args]), captures[:tree]]
|
56
|
+
}
|
57
|
+
rule(:command => simple(:command), :text_argument => simple(:ta)) {
|
58
|
+
[command.to_s, { "text" => ta }, []]
|
59
|
+
}
|
60
|
+
rule(:metadata => subtree(:metadata), :tree => subtree(:tree)) { |captures|
|
61
|
+
metadata = (captures[:metadata] || {}).reduce(:merge)
|
62
|
+
(metadata || {}).merge("definition" => captures[:tree])
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|