sfp 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.
- data/.gitignore +1 -0
- data/LICENSE +30 -0
- data/README.md +200 -0
- data/bin/sfp +24 -0
- data/bin/solver/linux/downward +0 -0
- data/bin/solver/linux/preprocess +0 -0
- data/bin/solver/macos/downward +0 -0
- data/bin/solver/macos/preprocess +0 -0
- data/lib/sfp/SfpLangLexer.rb +3127 -0
- data/lib/sfp/SfpLangParser.rb +9770 -0
- data/lib/sfp/Sfplib.rb +357 -0
- data/lib/sfp/parser.rb +128 -0
- data/lib/sfp/planner.rb +460 -0
- data/lib/sfp/sas.rb +966 -0
- data/lib/sfp/sas_translator.rb +1836 -0
- data/lib/sfp/sfw2graph.rb +168 -0
- data/lib/sfp/visitors.rb +132 -0
- data/lib/sfp.rb +17 -0
- data/sfp.gemspec +26 -0
- data/src/SfpLang.g +1005 -0
- data/src/build.sh +7 -0
- data/test/cloud-classes.sfp +77 -0
- data/test/cloud1.sfp +33 -0
- data/test/cloud2.sfp +41 -0
- data/test/cloud3.sfp +42 -0
- data/test/service-classes.sfp +151 -0
- data/test/service1.sfp +24 -0
- data/test/service3.sfp +27 -0
- data/test/task.sfp +22 -0
- data/test/test.inc +9 -0
- data/test/test.sfp +13 -0
- data/test/test1.sfp +19 -0
- data/test/test2.inc +40 -0
- data/test/test2.sfp +17 -0
- data/test/types.sfp +28 -0
- data/test/v1.1.sfp +22 -0
- metadata +120 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Nuri
|
7
|
+
module Planner
|
8
|
+
module Graph
|
9
|
+
ActionColor = 'white'
|
10
|
+
ActionLabelWithParameters = false
|
11
|
+
|
12
|
+
def self.clean(value)
|
13
|
+
return value[2, value.length-2] if value[0,2] == '$.'
|
14
|
+
return value
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get_label(action, withparameters=true)
|
18
|
+
label = clean(action["name"])
|
19
|
+
if withparameters and ActionLabelWithParameters
|
20
|
+
label += "("
|
21
|
+
if action["parameters"].length > 0
|
22
|
+
action["parameters"].each { |key,value|
|
23
|
+
label += "#{clean(key)}=#{clean(value.to_s)},"
|
24
|
+
}
|
25
|
+
label.chop!
|
26
|
+
end
|
27
|
+
label += ')'
|
28
|
+
end
|
29
|
+
return label
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.partial2dot(json)
|
33
|
+
dot = "digraph {\n"
|
34
|
+
|
35
|
+
dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
|
36
|
+
dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
|
37
|
+
last_actions = Hash.new
|
38
|
+
json["workflow"].each { |action|
|
39
|
+
dot += "#{action["id"]}[label=\"#{get_label(action)}\", shape=rect, style=filled, fillcolor=#{ActionColor}];\n"
|
40
|
+
last_actions[action["id"].to_s] = action
|
41
|
+
}
|
42
|
+
|
43
|
+
json["workflow"].each { |action|
|
44
|
+
has_predecessor = false
|
45
|
+
action["predecessors"].each { |prevId|
|
46
|
+
dot += "#{prevId} -> #{action["id"]};\n"
|
47
|
+
has_predecessor = true
|
48
|
+
last_actions.delete(prevId.to_s)
|
49
|
+
}
|
50
|
+
if not has_predecessor
|
51
|
+
dot += "init_state -> #{action["id"]};\n"
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
last_actions.each { |id,action|
|
56
|
+
dot += "#{id} -> final_state;\n"
|
57
|
+
}
|
58
|
+
|
59
|
+
dot += "}"
|
60
|
+
|
61
|
+
return dot
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.stage2dot(json)
|
65
|
+
dot = "digraph {\n"
|
66
|
+
|
67
|
+
dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
|
68
|
+
dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
|
69
|
+
index = 0
|
70
|
+
prevState = "init_state"
|
71
|
+
json["workflow"].each { |stage|
|
72
|
+
id = 0
|
73
|
+
stage.each { |action|
|
74
|
+
dot += "a" + index.to_s + "a" + id.to_s + '[label="' + get_label(action) + '", shape=rect]' + ";\n"
|
75
|
+
dot += prevState + " -> a" + index.to_s + "a" + id.to_s + ";\n"
|
76
|
+
id += 1
|
77
|
+
}
|
78
|
+
if index < json["workflow"].length-1
|
79
|
+
dot += "state" + index.to_s + ' [label="", shape=circle, fixedsize=true, width=0.35]' + ";\n"
|
80
|
+
prevState = "state" + index.to_s
|
81
|
+
id = 0
|
82
|
+
stage.each { |action|
|
83
|
+
dot += "a" + index.to_s + "a" + id.to_s + " -> " + prevState + ";\n"
|
84
|
+
id += 1
|
85
|
+
}
|
86
|
+
else
|
87
|
+
id = 0
|
88
|
+
stage.each { |action|
|
89
|
+
dot += "a" + index.to_s + "a" + id.to_s + " -> final_state;\n"
|
90
|
+
id += 1
|
91
|
+
}
|
92
|
+
end
|
93
|
+
index += 1
|
94
|
+
}
|
95
|
+
dot += "}"
|
96
|
+
return dot
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.sequential2dot(json)
|
100
|
+
dot = "digraph {\n"
|
101
|
+
|
102
|
+
dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
|
103
|
+
dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
|
104
|
+
id = 0
|
105
|
+
json["workflow"].each { |action|
|
106
|
+
dot += id.to_s + '[label="' + get_label(action) + '", shape=rect]' + ";\n"
|
107
|
+
id += 1
|
108
|
+
}
|
109
|
+
|
110
|
+
id = 0
|
111
|
+
prevActionId = nil
|
112
|
+
json["workflow"].each { |action|
|
113
|
+
if id == 0
|
114
|
+
dot += "init_state -> " + id.to_s + ";\n"
|
115
|
+
elsif id == json["workflow"].length-1
|
116
|
+
dot += id.to_s + " -> final_state;\n"
|
117
|
+
end
|
118
|
+
if prevActionId != nil
|
119
|
+
dot += prevActionId.to_s + " -> " + id.to_s + ";\n"
|
120
|
+
end
|
121
|
+
prevActionId = id
|
122
|
+
id += 1
|
123
|
+
}
|
124
|
+
dot += "}"
|
125
|
+
return dot
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def main
|
132
|
+
if ARGV.length < 1
|
133
|
+
puts "Usage: sfw2dot.rb <input-file> [output-file]\n\n"
|
134
|
+
exit
|
135
|
+
end
|
136
|
+
|
137
|
+
fp = open(ARGV[0], "rb")
|
138
|
+
json = JSON.parse(fp.read)
|
139
|
+
fp.close
|
140
|
+
|
141
|
+
dot = ""
|
142
|
+
case json["type"]
|
143
|
+
when 'partial-order', 'parallel'
|
144
|
+
dot = Nuri::Planner::Graph::partial2dot(json)
|
145
|
+
when 'sequential'
|
146
|
+
dot = Nuri::Planner::Graph::sequential2dot(json)
|
147
|
+
when 'stage'
|
148
|
+
dot = Nuri::Planner::Graph::stage2dot(json)
|
149
|
+
else
|
150
|
+
throw Exception, "Unrecognised type of workflow: #{json['type']}"
|
151
|
+
end
|
152
|
+
|
153
|
+
outfile = "/tmp/#{ARGV[0]}.dot"
|
154
|
+
fp = open(outfile, "w");
|
155
|
+
fp.write(dot)
|
156
|
+
fp.close
|
157
|
+
|
158
|
+
cmd = 'dot -Tpng -o';
|
159
|
+
if ARGV.length > 1
|
160
|
+
cmd += "#{ARGV[1]} #{outfile}"
|
161
|
+
else
|
162
|
+
cmd += ARGV[0].sub(/\.[a-zA-Z]*/,'') + ".png < #{outfile}"
|
163
|
+
end
|
164
|
+
system(cmd)
|
165
|
+
File.delete(outfile)
|
166
|
+
end
|
167
|
+
|
168
|
+
main if __FILE__ == $0
|
data/lib/sfp/visitors.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
module Sfp
|
2
|
+
module Visitor
|
3
|
+
# An example of class Visitor which can be used to traverse from one node to
|
4
|
+
# another node (bread first). This is based on 'Visitor' pattern.
|
5
|
+
class Default
|
6
|
+
def visit(name, value, parent); end
|
7
|
+
end
|
8
|
+
|
9
|
+
class ConformantVariables
|
10
|
+
attr_reader :var_values
|
11
|
+
def initialize
|
12
|
+
@var_values = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit(name, value, parent)
|
16
|
+
if value.is_a?(Hash) and value.iseither
|
17
|
+
ref = parent.ref.push(name)
|
18
|
+
@var_values[ref] = value['_values']
|
19
|
+
end
|
20
|
+
return true if value.is_a?(Hash) and value.isobject
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# eliminate '_parent' key/value from a Hash object to avoid cyclic references
|
26
|
+
class ParentEliminator
|
27
|
+
def visit(name, value, parent)
|
28
|
+
value.delete('_parent') if value.is_a?(Hash) and value.has_key?('_parent')
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ParentGenerator
|
34
|
+
def visit(name, value, parent)
|
35
|
+
value['_parent'] = parent if value.is_a?(Hash)
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ProcedureEliminator
|
41
|
+
def visit(name, value, parent)
|
42
|
+
if value.is_a?(Hash) and value.isprocedure
|
43
|
+
parent.delete(name)
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class PrettyStateGenerator
|
51
|
+
def visit(name, value, parent)
|
52
|
+
if name[0,1] == '_'
|
53
|
+
parent.delete(name)
|
54
|
+
elsif value.is_a?(Hash)
|
55
|
+
if value.isnull
|
56
|
+
parent[name] = nil
|
57
|
+
return false
|
58
|
+
else
|
59
|
+
parent.delete(name) if value['_context'] == 'procedure' or
|
60
|
+
value['_context'] == 'constraint'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class SfpGenerator
|
68
|
+
def initialize(root)
|
69
|
+
@root = root
|
70
|
+
end
|
71
|
+
|
72
|
+
def visit(name, value, parent)
|
73
|
+
if value.is_a?(Hash)
|
74
|
+
value['_parent'] = parent
|
75
|
+
value['_self'] = name
|
76
|
+
if not value.has_key?('_context')
|
77
|
+
value['_context'] = 'object'
|
78
|
+
if value.has_key?('_isa')
|
79
|
+
else
|
80
|
+
value['_isa'] = '$.Object'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
Sfp::Helper.expand_object(value, @root) if value.isobject
|
84
|
+
end
|
85
|
+
return true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class ReferenceModifier
|
90
|
+
def visit(name, value, parent)
|
91
|
+
if value.is_a?(String) and value.isref and parent.isobject
|
92
|
+
if value.length >= 8 and value[0,8] == '$.parent'
|
93
|
+
_, _, rest = value.split('.', 3)
|
94
|
+
if parent.has_key?('_parent')
|
95
|
+
parent[name] = parent['_parent'].ref + (rest == nil ? '' : '.' + rest)
|
96
|
+
else
|
97
|
+
raise Exception
|
98
|
+
end
|
99
|
+
elsif value.length >= 6 and value[0,6] == '$.this'
|
100
|
+
_, _, rest = value.split('.', 3)
|
101
|
+
parent[name] = parent.ref + (rest == nil ? '' : '.' + rest)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class SetModifier
|
109
|
+
def visit(name, value, parent)
|
110
|
+
return false if value.is_a?(Hash) and value.isprocedure
|
111
|
+
|
112
|
+
if value.is_a?(Hash) and value.isset
|
113
|
+
parent[name] = []
|
114
|
+
return false
|
115
|
+
end
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class NullModifier
|
121
|
+
def visit(name, value, parent)
|
122
|
+
return false if value.is_a?(Hash) and value.isprocedure
|
123
|
+
|
124
|
+
if value.is_a?(Hash) and value.isnull
|
125
|
+
parent[name] = nil
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
true
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/sfp.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# external dependencies
|
2
|
+
require 'rubygems'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# internal dependencies
|
6
|
+
libdir = File.expand_path(File.dirname(__FILE__))
|
7
|
+
|
8
|
+
require libdir + '/sfp/Sfplib'
|
9
|
+
require libdir + '/sfp/SfpLangParser'
|
10
|
+
require libdir + '/sfp/SfpLangLexer'
|
11
|
+
|
12
|
+
require libdir + '/sfp/visitors'
|
13
|
+
require libdir + '/sfp/sas_translator'
|
14
|
+
require libdir + '/sfp/parser'
|
15
|
+
|
16
|
+
require libdir + '/sfp/sas'
|
17
|
+
require libdir + '/sfp/planner'
|
data/sfp.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'sfp'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.date = '2013-04-20'
|
5
|
+
s.summary = 'SFP Parser and Planner'
|
6
|
+
s.description = 'A Ruby gem that provides a Ruby interface to parse SFP language. ' +
|
7
|
+
'It also provides a Ruby interface for a solver to generate a workflow ' +
|
8
|
+
'for a planning task written in SFP language.'
|
9
|
+
s.authors = ['Herry']
|
10
|
+
s.email = 'herry13@gmail.com'
|
11
|
+
|
12
|
+
s.executables << 'sfp'
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
15
|
+
|
16
|
+
`git ls-files -- bin/*`.split("\n") { |f|
|
17
|
+
s.executables << f
|
18
|
+
}
|
19
|
+
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.homepage = 'https://github.com/herry13/sfp-ruby'
|
23
|
+
|
24
|
+
s.add_dependency 'json', '~> 1.7.5'
|
25
|
+
s.add_dependency 'antlr3', '~> 1.8.12'
|
26
|
+
end
|