sfp 0.1.0

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