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
data/lib/sfp/Sfplib.rb
ADDED
@@ -0,0 +1,357 @@
|
|
1
|
+
module Sfp
|
2
|
+
module SfpLangHelper
|
3
|
+
attr_accessor :root_dir, :home_dir
|
4
|
+
attr_reader :root, :used_classes, :arrays, :conformant
|
5
|
+
|
6
|
+
def init
|
7
|
+
@root = Hash.new
|
8
|
+
@now = @root
|
9
|
+
@id = 0
|
10
|
+
@root['Object'] = { '_self' => 'Object', '_context' => 'class', '_parent' => @root }
|
11
|
+
@unexpanded_classes = Array.new
|
12
|
+
@used_classes = Array.new
|
13
|
+
@arrays = Hash.new
|
14
|
+
@conformant = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def next_id
|
18
|
+
nid = "c" + @id.to_s
|
19
|
+
@id += 1
|
20
|
+
return nid
|
21
|
+
end
|
22
|
+
|
23
|
+
def null_value(isa=nil)
|
24
|
+
return { '_context' => 'null', '_isa' => isa }
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_ref(path)
|
28
|
+
ref = "$." + path
|
29
|
+
return ref
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_sfp
|
33
|
+
return @root
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_constraint(name, type='and')
|
37
|
+
return { '_self' => name,
|
38
|
+
'_context' => 'constraint',
|
39
|
+
'_type' => type,
|
40
|
+
'_parent' => @now
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_file(file)
|
45
|
+
filepath = file
|
46
|
+
filepath = @root_dir + "/" + file if not @root_dir.nil? and file[0,1] != '/'
|
47
|
+
filepath = @home_dir + "/" + file if not @home_dir.nil? and not File.exist?(filepath)
|
48
|
+
|
49
|
+
raise Exception, 'File not found: ' + file if not File.exist?(filepath)
|
50
|
+
|
51
|
+
new_home_dir = File.expand_path(File.dirname(filepath))
|
52
|
+
parser = Sfp::Parser.new({:root_dir => @root_dir, :home_dir => new_home_dir})
|
53
|
+
parser.parse(File.read(filepath))
|
54
|
+
|
55
|
+
parser.root.each_pair { |key,val|
|
56
|
+
if val['_context'] == 'class' or val['_context'] == 'composite'
|
57
|
+
@root[key] = val
|
58
|
+
elsif val['_context'] == 'state' or val['_context'] == 'constraint'
|
59
|
+
if @root.has_key?(key)
|
60
|
+
if @root[key]['_context'] != val['_context']
|
61
|
+
@root[key] = val
|
62
|
+
else
|
63
|
+
val['_context'].each_pair { |k2,v2| @root[key][k2] = v2 }
|
64
|
+
end
|
65
|
+
else
|
66
|
+
@root[key] = val
|
67
|
+
end
|
68
|
+
end
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def goto_parent(remove_parent=false)
|
73
|
+
n = @now
|
74
|
+
@now = @now['_parent']
|
75
|
+
n.delete('_parent') if remove_parent
|
76
|
+
return n
|
77
|
+
end
|
78
|
+
|
79
|
+
def expand_classes
|
80
|
+
@unexpanded_classes.each { |c|
|
81
|
+
sclass = @root.at?(c['_extends'])
|
82
|
+
c.inherits( sclass )
|
83
|
+
c['_super'] = (sclass.has_key?('_super') ? sclass['_super'].clone : Array.new)
|
84
|
+
c['_super'] << c['_extends']
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def expand_object(obj)
|
89
|
+
return false if not Sfp::Helper.expand_object(obj, @root)
|
90
|
+
@used_classes = @used_classes.concat(obj['_classes']).uniq
|
91
|
+
end
|
92
|
+
|
93
|
+
def deep_clone(value)
|
94
|
+
if value.is_a?(Hash)
|
95
|
+
result = value.clone
|
96
|
+
value.each { |k,v|
|
97
|
+
if k != '_parent'
|
98
|
+
result[k] = deep_clone(v)
|
99
|
+
result[k]['_parent'] = result if result[k].is_a?(Hash) and result[k].has_key?('_parent')
|
100
|
+
end
|
101
|
+
}
|
102
|
+
result
|
103
|
+
elsif value.is_a?(Array)
|
104
|
+
result = Array.new
|
105
|
+
value.each { |v| result << deep_clone(v) }
|
106
|
+
result
|
107
|
+
else
|
108
|
+
value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module Helper
|
114
|
+
def self.deep_clone(value)
|
115
|
+
if value.is_a?(Hash)
|
116
|
+
result = value.clone
|
117
|
+
value.each { |k,v|
|
118
|
+
if k != '_parent'
|
119
|
+
result[k] = deep_clone(v)
|
120
|
+
result[k]['_parent'] = result if result[k].is_a?(Hash) and result[k].has_key?('_parent')
|
121
|
+
end
|
122
|
+
}
|
123
|
+
result
|
124
|
+
elsif value.is_a?(Array)
|
125
|
+
result = Array.new
|
126
|
+
value.each { |v| result << deep_clone(v) }
|
127
|
+
result
|
128
|
+
else
|
129
|
+
value
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.to_json(sfp)
|
134
|
+
root = Sfp::Helper.deep_clone(sfp)
|
135
|
+
root.accept(Sfp::Visitor::ParentEliminator.new)
|
136
|
+
return JSON.generate(root)
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.to_pretty_json(sfp)
|
140
|
+
root = Sfp::Helper.deep_clone(sfp)
|
141
|
+
root.accept(Sfp::Visitor::ParentEliminator.new)
|
142
|
+
return JSON.pretty_generate(root)
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.expand_object(obj, root)
|
146
|
+
return false if obj == nil or root == nil or
|
147
|
+
not obj.has_key?('_isa') or obj['_isa'] == nil
|
148
|
+
objclass = root.at?(obj['_isa'])
|
149
|
+
if objclass.nil? or objclass.is_a?(Sfp::Unknown) or objclass.is_a?(Sfp::Undefined)
|
150
|
+
raise Exception, 'Super class is not found: ' + obj['_self'] + ' < ' + obj['_isa']
|
151
|
+
end
|
152
|
+
obj.inherits( objclass )
|
153
|
+
obj['_classes'] = (objclass.has_key?('_super') ? objclass['_super'].clone : Array.new)
|
154
|
+
obj['_classes'] << obj['_isa']
|
155
|
+
return true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
class Null
|
161
|
+
attr_accessor :type
|
162
|
+
end
|
163
|
+
|
164
|
+
# Instance of this class will be returned as the value of a non-exist variable
|
165
|
+
class Undefined
|
166
|
+
attr_accessor :path
|
167
|
+
def initialize(path=nil); @path = path; end
|
168
|
+
def to_s; (@path.nil? ? "<sfp::undefined>" : "<sfp::undefined[#{@path}]>"); end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Instance of this class will be return as the value of an unknown variable
|
172
|
+
# in open-world assumption.
|
173
|
+
class Unknown
|
174
|
+
attr_accessor :path
|
175
|
+
def initialize(path=nil); @path = path; end
|
176
|
+
def to_s; (@path.nil? ? "<sfp::unknown>" : "<sfp::unknown[#{@path}]>"); end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# return a fullpath of reference of this context
|
181
|
+
Hash.send(:define_method, "ref") {
|
182
|
+
return '$' if not self.has_key?('_parent') or self['_parent'] == nil
|
183
|
+
me = (self.has_key?('_self') ? self['_self'] : '')
|
184
|
+
return self['_parent'].ref + "." + me
|
185
|
+
}
|
186
|
+
|
187
|
+
# accept method as implementation of Visitor pattern
|
188
|
+
Hash.send(:define_method, "accept") { |visitor|
|
189
|
+
keys = self.keys
|
190
|
+
keys.each do |key|
|
191
|
+
next if key == '_parent' or not self.has_key?(key)
|
192
|
+
value = self[key]
|
193
|
+
go_next = visitor.visit(key, value, self)
|
194
|
+
value.accept(visitor) if value.is_a?(Hash) and go_next == true
|
195
|
+
end
|
196
|
+
}
|
197
|
+
|
198
|
+
# resolve a reference, return nil if there's no value with given address
|
199
|
+
Hash.send(:define_method, "at?") { |addr|
|
200
|
+
return Sfp::Unknown.new if not addr.is_a?(String)
|
201
|
+
|
202
|
+
addrs = addr.split('.', 2)
|
203
|
+
|
204
|
+
if addrs[0] == '$'
|
205
|
+
return self.root.at?(addrs[1])
|
206
|
+
elsif addrs[0] == 'root'
|
207
|
+
return self.root.at?(addrs[1])
|
208
|
+
elsif addrs[0] == 'this' or addrs[0] == 'self'
|
209
|
+
return self.at?(addrs[1])
|
210
|
+
elsif addrs[0] == 'parent'
|
211
|
+
return nil if not self.has_key?('_parent')
|
212
|
+
return self['_parent'] if addrs[1].nil?
|
213
|
+
return self['_parent'].at?(addrs[1])
|
214
|
+
elsif self.has_key?(addrs[0])
|
215
|
+
if addrs.length == 1
|
216
|
+
return self[addrs[0]]
|
217
|
+
else
|
218
|
+
return self[addrs[0]].at?(addrs[1]) if self[addrs[0]].is_a?(Hash) and addrs[1] != ''
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
return Sfp::Unknown.new
|
223
|
+
}
|
224
|
+
|
225
|
+
Hash.send(:define_method, "type?") { |name|
|
226
|
+
return nil if not self.has_key?(name)
|
227
|
+
value = self[name]
|
228
|
+
if value != nil
|
229
|
+
return '$.Boolean' if value.is_a?(TrueClass) or value.is_a?(FalseClass)
|
230
|
+
return '$.Integer' if value.is_a?(Numeric)
|
231
|
+
return '$.String' if value.is_a?(String) and not value.isref
|
232
|
+
return value['_isa'] if value.isobject or value.isnull
|
233
|
+
return "(#{value['_isa']})" if value.is_a?(Hash) and value.isset and value.has_key?('_isa')
|
234
|
+
end
|
235
|
+
|
236
|
+
return nil
|
237
|
+
}
|
238
|
+
|
239
|
+
# return root context of this context
|
240
|
+
Hash.send(:define_method, "root") {
|
241
|
+
return self if not self.has_key?('_parent') or self['_parent'] == nil
|
242
|
+
return self['_parent'].root
|
243
|
+
}
|
244
|
+
|
245
|
+
# return true if this context is a constraint, otherwise false
|
246
|
+
Hash.send(:define_method, "isconstraint") {
|
247
|
+
return (self.has_key?('_context') and self['_context'] == 'constraint')
|
248
|
+
}
|
249
|
+
|
250
|
+
Hash.send(:define_method, "isset") {
|
251
|
+
return (self.has_key?('_context') and self['_context'] == 'set')
|
252
|
+
}
|
253
|
+
|
254
|
+
Hash.send(:define_method, "isprocedure") {
|
255
|
+
return (self.has_key?('_context') and self['_context'] == 'procedure')
|
256
|
+
}
|
257
|
+
|
258
|
+
# return true if this context is a class, otherwise false
|
259
|
+
Hash.send(:define_method, "isclass") {
|
260
|
+
return (self.has_key?('_context') and self['_context'] == 'class')
|
261
|
+
}
|
262
|
+
|
263
|
+
# return superclass' reference if this context is a sub-class, otherwise nil
|
264
|
+
Hash.send(:define_method, "extends") {
|
265
|
+
return self['_extends'] if self.isclass and self.has_key?('_extends')
|
266
|
+
return nil
|
267
|
+
}
|
268
|
+
|
269
|
+
# return true if this class has been expanded, otherwise false
|
270
|
+
Hash.send(:define_method, "expanded") {
|
271
|
+
@expanded = false if not defined?(@expanded)
|
272
|
+
return @expanded
|
273
|
+
}
|
274
|
+
|
275
|
+
# copy attributes and procedures from superclass to itself
|
276
|
+
Hash.send(:define_method, 'inherits') { |parent|
|
277
|
+
return if not parent.is_a?(Hash)
|
278
|
+
parent.each_pair { |key,value|
|
279
|
+
next if key[0,1] == '_' or self.has_key?(key)
|
280
|
+
if value.is_a?(Hash)
|
281
|
+
self[key] = Sfp::Helper.deep_clone(value)
|
282
|
+
self[key]['_parent'] = self
|
283
|
+
else
|
284
|
+
self[key] = value
|
285
|
+
end
|
286
|
+
}
|
287
|
+
@expanded = true
|
288
|
+
}
|
289
|
+
|
290
|
+
# return true if this context is an object, otherwise false
|
291
|
+
Hash.send(:define_method, 'isobject') {
|
292
|
+
return (self.has_key?('_context') and self['_context'] == 'object')
|
293
|
+
}
|
294
|
+
|
295
|
+
Hash.send(:define_method, 'isa') {
|
296
|
+
return nil if not self.isobject or not self.has_key?('_isa')
|
297
|
+
return self['_isa']
|
298
|
+
}
|
299
|
+
|
300
|
+
Hash.send(:define_method, 'isvalue') {
|
301
|
+
return self.isobject
|
302
|
+
}
|
303
|
+
|
304
|
+
Hash.send(:define_method, 'isnull') {
|
305
|
+
return (self.has_key?('_context') and self['_context'] == 'null')
|
306
|
+
}
|
307
|
+
|
308
|
+
Hash.send(:define_method, 'iseither') {
|
309
|
+
return (self['_context'] == 'either')
|
310
|
+
}
|
311
|
+
|
312
|
+
Hash.send(:define_method, 'tostring') {
|
313
|
+
return 'null' if self.isnull
|
314
|
+
return self.ref
|
315
|
+
}
|
316
|
+
|
317
|
+
# add path to the end of a reference
|
318
|
+
String.send(:define_method, "push") { |value|
|
319
|
+
return self.to_s + "." + value
|
320
|
+
}
|
321
|
+
|
322
|
+
# return first element and keep the rest
|
323
|
+
String.send(:define_method, 'explode') {
|
324
|
+
return self.split('.', 2)
|
325
|
+
}
|
326
|
+
|
327
|
+
# return an array of [ parent, last_element]
|
328
|
+
String.send(:define_method, 'pop_ref') {
|
329
|
+
return self.extract
|
330
|
+
}
|
331
|
+
|
332
|
+
# return an array of [ parent, last_element ]
|
333
|
+
String.send(:define_method, 'extract') {
|
334
|
+
return self if not self.isref
|
335
|
+
parts = self.split('.')
|
336
|
+
return self if parts.length <= 1
|
337
|
+
last = parts[ parts.length - 1 ]
|
338
|
+
len = self.length - last.length - 1
|
339
|
+
return [self[0, len], last]
|
340
|
+
}
|
341
|
+
|
342
|
+
|
343
|
+
# return true if this string is a reference, otherwise false
|
344
|
+
String.send(:define_method, 'isref') {
|
345
|
+
s = self.to_s
|
346
|
+
return true if (s.length > 0 and s[0,1] == '$')
|
347
|
+
return false
|
348
|
+
}
|
349
|
+
|
350
|
+
# return the parent of this path
|
351
|
+
# e.g.: if self == 'a.b.c.d', it will return 'a.b.c'
|
352
|
+
String.send(:define_method, 'to_top') {
|
353
|
+
return self if self == '$'
|
354
|
+
parts = self.split('.')
|
355
|
+
return self[0, self.length - parts[parts.length-1].length - 1]
|
356
|
+
}
|
357
|
+
|
data/lib/sfp/parser.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
module Sfp
|
2
|
+
# main class which processes configuration description in SFP language either
|
3
|
+
# in file or as a string
|
4
|
+
class Parser
|
5
|
+
# enable this class to process SFP into FDR (SAS+)
|
6
|
+
include Sfp::SasTranslator
|
7
|
+
|
8
|
+
attr_accessor :root_dir, :home_dir, :conformant
|
9
|
+
|
10
|
+
def initialize(params={})
|
11
|
+
@root_dir = (params[:root_dir].is_a?(String) ?
|
12
|
+
params[:root_dir].strip :
|
13
|
+
nil)
|
14
|
+
@home_dir = (params[:home_dir].is_a?(String) ?
|
15
|
+
params[:home_dir].strip :
|
16
|
+
nil)
|
17
|
+
@root = params[:root]
|
18
|
+
@conformant = !!params[:conformant]
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param string : a string in SFP language
|
22
|
+
def parse(string)
|
23
|
+
lexer = SfpLang::Lexer.new(string)
|
24
|
+
tokens = ANTLR3::CommonTokenStream.new(lexer)
|
25
|
+
parser = SfpLang::Parser.new(tokens)
|
26
|
+
parser.root_dir = @root_dir
|
27
|
+
parser.home_dir = @home_dir
|
28
|
+
parser.sfp
|
29
|
+
@root = parser.root
|
30
|
+
@conformant = parser.conformant
|
31
|
+
@parser_arrays = parser.arrays
|
32
|
+
end
|
33
|
+
|
34
|
+
=begin
|
35
|
+
# Parse SFP file and return its JSON representation
|
36
|
+
def self.parse_file(file)
|
37
|
+
return file_to_sfp(file)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.file_to_sfp(file)
|
41
|
+
parser = Parser.new
|
42
|
+
parser.parse_file(file)
|
43
|
+
return parser.to_sfp
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.to_sfp(string)
|
47
|
+
parser = Parser.new
|
48
|
+
parser.parse(string)
|
49
|
+
return parser.to_sfp
|
50
|
+
end
|
51
|
+
|
52
|
+
# parse SFP file
|
53
|
+
def parse_file(file)
|
54
|
+
f = File.open(file, 'rb')
|
55
|
+
lexer = SFP::Lexer.new(f)
|
56
|
+
tokens = ANTLR3::CommonTokenStream.new(lexer)
|
57
|
+
parser = SFP::Parser.new(tokens)
|
58
|
+
parser.root_dir = (@root_dir == nil or @root_dir == '' ?
|
59
|
+
File.expand_path('.') : @root_dir)
|
60
|
+
parser.home_dir = File.dirname(f.path)
|
61
|
+
parser.sfp
|
62
|
+
@conformant = parser.conformant
|
63
|
+
@root = parser.root
|
64
|
+
@parser_arrays = parser.arrays
|
65
|
+
end
|
66
|
+
|
67
|
+
# parse SFP in a string
|
68
|
+
def parse(text)
|
69
|
+
lexer = SFP::Lexer.new(text)
|
70
|
+
tokens = ANTLR3::CommonTokenStream.new(lexer)
|
71
|
+
parser = SFP::Parser.new(tokens)
|
72
|
+
parser.root_dir = (@root_dir == nil or @root_dir == '' ?
|
73
|
+
File.expand_path(File.dirname('.')) : @root_dir)
|
74
|
+
parser.home_dir = parser.root_dir
|
75
|
+
parser.sfp
|
76
|
+
@root = parser.root
|
77
|
+
@parser_arrays = parser.arrays
|
78
|
+
end
|
79
|
+
|
80
|
+
# dump the parsed specification into standard output
|
81
|
+
def dump(root=nil)
|
82
|
+
return if root == nil
|
83
|
+
root = Nuri::Sfp.deep_clone(@root)
|
84
|
+
root.accept(ParentEliminator.new)
|
85
|
+
puts JSON.pretty_generate(root)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.dump(root)
|
89
|
+
return if root == nil
|
90
|
+
root = Nuri::Sfp.deep_clone(root)
|
91
|
+
root.accept(ParentEliminator.new)
|
92
|
+
puts JSON.pretty_generate(root)
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_sfp
|
96
|
+
@root
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_json
|
100
|
+
root = self.to_sfp
|
101
|
+
return if root == nil
|
102
|
+
root = Nuri::Sfp.deep_clone(root)
|
103
|
+
return Nuri::Sfp.to_json(root)
|
104
|
+
end
|
105
|
+
=end
|
106
|
+
|
107
|
+
def to_json(params={})
|
108
|
+
return '' if @root.nil?
|
109
|
+
return Sfp::Helper.to_pretty_json(@root) if params[:pretty]
|
110
|
+
return Sfp::Helper.to_json(@root)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
=begin
|
115
|
+
def self.to_json(sfp)
|
116
|
+
root = Sfp::Helper.deep_clone(sfp)
|
117
|
+
root.accept(Nuri::Sfp::ParentEliminator.new)
|
118
|
+
return JSON.generate(root)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.to_pretty_json(sfp)
|
122
|
+
root = Nuri::Sfp.deep_clone(sfp)
|
123
|
+
root.accept(Nuri::Sfp::ParentEliminator.new)
|
124
|
+
return JSON.pretty_generate(root)
|
125
|
+
end
|
126
|
+
=end
|
127
|
+
|
128
|
+
end
|