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