swarm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|