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.
@@ -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