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