tla-parser-s 0.1.2 → 0.2.2

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.
@@ -27,9 +27,12 @@ module TlaParserS
27
27
  # global context to use in resolving.
28
28
  #
29
29
  # constructor:
30
- # #initContext: add initContext
31
- # #initSnippets: parse TLA+ snippets to context
32
- # #resolveModules: resolve modules
30
+ # Initialize & parse
31
+ # #initContext: add initContext
32
+ # #initSnippets: parse TLA+ snippets to context
33
+ # Services available after init & parse
34
+ # #resolveModules: resolve modules
35
+ # #sortModule:
33
36
  #
34
37
  # ******************************************************************
35
38
 
@@ -37,15 +40,22 @@ module TlaParserS
37
40
  # ------------------------------------------------------------------
38
41
  # Logger
39
42
 
40
- PROGNAME = "resolver" # progname for logger
43
+ PROGNAME = nil # progname default class-name
41
44
  include TlaParserS::Utils::MyLogger # mix logger
42
45
 
43
46
  # ------------------------------------------------------------------
44
- # constructore
47
+
48
+ # @!group Construct && configure
49
+
50
+ # construct with 'options'
51
+ #
52
+ # @param options [Hash] to configure resolver
53
+ # @option options [String] :logfile name of file where to log to
54
+ #
45
55
  def initialize( options={} )
46
56
 
47
57
  @logger = getLogger( PROGNAME, options )
48
- @logger.info( "#{__method__} initialized" )
58
+ @logger.info( "#{__method__} initialized parser-resolver version #{TlaParserS::version}" )
49
59
 
50
60
  @report_unresolved = options[:report_unresolved]
51
61
 
@@ -60,37 +70,12 @@ module TlaParserS
60
70
  @directive_modules = []
61
71
  end
62
72
 
63
- # Parse entry ie. file or possibly a string
64
- #
65
- # @param entry [File|String] to parse
66
- # @param moduleName [String] identifying 'entry' for error messages
67
- # @return [Snippets] parsed syntax tree node
68
- def parseSnippets( entry, moduleName )
69
- begin
70
- parser.parse( entry )
71
- rescue ParseException => ee
72
- msg = "Parsing '#{moduleName}' results to error \n\n#{ee.message}"
73
- @logger.error( "#{__method__} #{msg}" )
74
- raise ParseException.new( ee ), msg, ee.backtrace
75
- end
76
-
77
- end
73
+ # @!endgroup
78
74
 
79
- # Add of definition names in 'snippets' to '@directives', and
80
- # 'moduleName' to '@directive_modules' if any 'snippets' contained
81
- # 'directive_definitions'
82
- #
83
- # @param moduleName [String] name of module where 'snippets' are parses
84
- # @param snippets [Snippets] parsed snippts, respons to :directive_definitions
85
- def addDirectives( snippets, moduleName )
86
- new_directives = snippets.directive_definitions
87
-
88
- @logger.debug( "#{__method__} moduleName=#{moduleName} -> new_directives=#{new_directives}" )
89
- @directives = directives + new_directives
90
-
91
- @directive_modules << moduleName if new_directives && new_directives.any?
92
- end
75
+ # ------------------------------------------------------------------
93
76
 
77
+ # @!group Initialize context & parse snippets
78
+
94
79
  # Build initial context (TLA+ reserved words, etc)
95
80
  #
96
81
  # @param names [String:Array] to put context
@@ -107,46 +92,57 @@ module TlaParserS
107
92
  }
108
93
  end
109
94
  )
110
-
111
95
 
112
96
  end
113
97
 
114
-
115
- # ------------------------------------------------------------------
116
98
  # Load TLA+ snippets from file (or strings in unit test), and
117
99
  # build global context to use in resolving.
118
100
  #
119
- # @param entries [ File:Array | String:Array] containg TLA+ snippets
101
+ # @param [File:Array|String:Array|File|String] entries containg TLA+ snippets
120
102
  #
121
103
  # @param blk [Block] yields entry
122
104
 
123
105
  def initSnippets( entries, &blk )
124
106
 
107
+ # Make it to an array
108
+ entries = [ entries ] unless entries.is_a?( Array ) || entries.nil?
109
+
110
+ @logger.info "#{__method__} starts in #{Dir.pwd}"
111
+
125
112
  # create global context
126
113
  context.pushContext
127
114
 
115
+ # collect symbol definitions parsed
116
+ parsedSnippets = []
117
+
118
+
128
119
  # iterate
129
120
  entries && entries.each_with_index do |entry,i|
130
121
 
131
122
  # skip directories
132
123
  # next if entry.is_a?( File ) && File.directory?( entry )
133
- next if File.directory?( entry )
124
+ next if (entry.is_a?( String ) || entry.is_a?( File )) && File.directory?( entry )
134
125
 
135
126
  # meaningfull modulename
136
- moduleName = entry.is_a?( File ) ? entry.path : "entries[#{i}]"
127
+ moduleName = entry.is_a?( File ) ? entry.path : entry.respond_to?(:moduleName) ? entry.moduleName : "entries[#{i}]"
137
128
 
138
- @logger.info( "#{__method__} entry=#{entry}" )
129
+ @logger.info( "#{__method__} entry=#{entry}, moduleName=#{moduleName}" )
139
130
  begin
140
131
 
141
132
  # String|File --> AST
142
133
  snippets = parseSnippets( entry, moduleName )
143
134
  yield( true, entry ) if blk
135
+ @logger.debug( "#{__method__} #{moduleName}: snippets=#{snippets.inspect}" ) if @logger.debug?
136
+
137
+ # collect an array of hashes with :node_type, :value, :true -properties
138
+ parsedSnippets += snippets.symbol_definitions
144
139
 
145
- # collect directive defitions
140
+ # collect '@directives' and, '@directive_modules' if
141
+ # snippets.directive_definitions because they should be
142
+ # included to entry points
146
143
  addDirectives( snippets, moduleName )
147
144
 
148
- # add to contex tree nodes, which respond to
149
- # 'symbol_definitions'
145
+ # add to contex tree nodes, which respond to 'symbol_definitions'
150
146
  context.addContext( snippets, moduleName )
151
147
 
152
148
  rescue TlaParserS::ParseException => e
@@ -159,10 +155,18 @@ module TlaParserS
159
155
 
160
156
  end
161
157
 
158
+ @logger.info "#{__method__} finished"
162
159
  self
160
+ parsedSnippets
163
161
  end
164
162
 
163
+
164
+ # @!endgroup
165
+
165
166
  # ------------------------------------------------------------------
167
+ # @!group Services to use after init & parse
168
+
169
+
166
170
  # Find modules 1) containing TLA+ snippets reachable from
167
171
  # 'entryPoints', 2) modules reachabable for directive defintision,
168
172
  # and 3) modules containing directive definitions
@@ -173,9 +177,132 @@ module TlaParserS
173
177
  definitionModules = resolveModulesDo( entryPoints + directives, &blk )
174
178
  return definitionModules + directive_modules
175
179
  end
180
+
181
+ # ------------------------------------------------------------------
182
+ # Run topologica sort on module
183
+ require 'tsort'
184
+ # Define a class to do topological sort
185
+ class THash < Hash
186
+ include TSort
187
+ alias tsort_each_node each_key
188
+ def tsort_each_child(node, &block)
189
+ fetch(node).each(&block)
190
+ end
191
+ end
192
+ # ------------------------------------------------------------------
176
193
 
194
+ # Run topological sort on modules.
195
+ #
196
+ # @param modules [String:Array] names of modules to sort
197
+ #
198
+ # @return [String:Array] of sorted modules
199
+ def sortModules( modules, &blk )
200
+
201
+ @logger.info "#{__method__}: modulesstart=#{modules} - starts"
202
+
203
+ sortteri =
204
+ modules.
205
+ #
206
+ # map { |m| puts "m0=#{m}"; m }.
207
+ #
208
+ # Find symbols in module :m to array :s
209
+ #
210
+ # [{ :m=<module-name>, :s=>[<symbol definitions in module>]}, ...]
211
+ #
212
+ map { |m| { :m => m, :s=> context.resolveModule(m) } }.
213
+ # map { |m| puts "m1[:s]=#{m[:s].map{ |s| s[:module] +':' + s[:symbol_name] } }"; m }.
214
+ #
215
+ # Resolver symbols in :s to array :r
216
+ #
217
+ # At this point
218
+ # [{ :m=<module-name>, :s=>[<symbol definitions in module>], :r=>[{:symbol=>"", :resolved>={} }]}, .... ]
219
+ #
220
+ map { |m| m[:r] = m[:s].map{ |s| resolveEntryPoint( s[:symbol_name] ) }.flatten; m }.
221
+ # map { |m| puts "#{m[:m]}-> m2[:s]=#{m[:s].map{ |s| s[:module] +':' + s[:symbol_name] } }"; m }.
222
+ # map { |m| puts "#{m[:m]}-> m2[:r]=#{m[:r].map { |r| r[:symbol] + '->' + r[:resolved][:symbol_name] + '@' + (r[:resolved][:module] ||'-' )}}"; m }.
223
+ #
224
+ # Drop unresolved symbols from :r
225
+ #
226
+ map { |m| m[:r] = m[:r].reject { |r| r[:resolved].nil? }; m}.
227
+ #
228
+ # Add m[:d] array of module names (drop resolves, which do not
229
+ # have module defined (correspoding to local references)
230
+ #
231
+ # [{ :m=<module-name>, :d=> [<resolved modules (filepaths?)>] :s=>[<symbol definitions in module>], :r=>[{:symbol=>"", :resolved>={} }]}, .... ]
232
+ #
233
+ #
234
+ map { |m| m[:d] = m[:r].select { |r| r[:resolved][:module]}.map { |r| r[:resolved][:module]}.uniq; m}.
235
+ # map { |m| puts "m3=#{m[:m]}, :d=#{m[:d]}, [:s]=#{m[:s].map{ |s| s[:module] +':' + s[:symbol_name] } }"; m }.
236
+ #
237
+ # Reject modules, which do not define any snippets
238
+ #
239
+ select { |m| m[:s].any? }.
240
+ # map { |m| puts "m4=#{m[:m]}, :d=#{m[:d]}, [:s]=#{m[:s].map{ |s| s[:module] +':' + s[:symbol_name] } }"; m }.
241
+ #
242
+ # clear prefix path from modulename in m[:d] elements
243
+ #
244
+ map { |m| m[:d] = m[:d].map{ |d| modules.select { |mo| d.end_with?(mo)}.first }.select {|d| d }; m }.
245
+ # map { |m| puts "m5=#{m[:m]}, :d=#{m[:d]}"; m }.
246
+ #
247
+ #
248
+ # Finally create a tsort hash
249
+ #
250
+ inject( THash.new ){ |t, m| t[m[:m]] = m[:d]; t }.
251
+ #
252
+ # And sort it
253
+ #
254
+ tsort
255
+
256
+ sortteri
257
+ end
258
+
259
+ # @!endgroup
260
+
261
+ # ------------------------------------------------------------------
262
+ # @!group Internal methods for parsing
263
+
264
+
265
+ # Parse entry ie. file or possibly a string
266
+ #
267
+ # @param entry [File|String] to parse
268
+ #
269
+ # @param moduleName [String] identifying 'entry' for error messages
270
+ #
271
+ # @return [Snippets] parsed syntax tree node
272
+ def parseSnippets( entry, moduleName )
273
+ begin
274
+ parser.parse( entry )
275
+ rescue ParseException => ee
276
+ msg = "Parsing '#{moduleName}' results to error \n\n#{ee.message}"
277
+ @logger.error( "#{__method__} #{msg}" )
278
+ raise ParseException.new( ee ), msg, ee.backtrace
279
+ end
280
+
281
+ end
282
+
283
+ # Add definition names in 'snippets' to '@directives', and
284
+ # 'moduleName' to '@directive_modules' if any of the 'snippets'
285
+ # contains 'directive_definitions'
286
+ #
287
+ # @param moduleName [String] name of module where 'snippets' are parses
288
+ # @param snippets [Snippets] parsed snippts, respons to :directive_definitions
289
+ def addDirectives( snippets, moduleName )
290
+ new_directives = snippets.directive_definitions
291
+
292
+ @logger.debug( "#{__method__} moduleName=#{moduleName} -> new_directives=#{new_directives}" ) if @logger.debug?
293
+ @directives = directives + new_directives
294
+
295
+ @directive_modules << moduleName if new_directives && new_directives.any?
296
+ end
297
+
298
+ # @!endgroup
299
+
300
+
177
301
 
178
302
  # ------------------------------------------------------------------
303
+
304
+ # @!group implement module resolve
305
+
179
306
  # Find modules containing TLA+ snippets needed to implement code
180
307
  # for 'entryPoints'. Notice: A module may include additional
181
308
  # entrypoint, which must also be satisfied.
@@ -189,7 +316,7 @@ module TlaParserS
189
316
  # @return [String:Arrays] of module names needed in implementation.
190
317
  def resolveModulesDo( entryPoints, &blk )
191
318
 
192
- @logger.info( "#{__method__} resolveModules for entryPoint=#{entryPoints.join(',')}" )
319
+ @logger.info( "#{__method__} resolveModules for entryPoint=#{entryPoints.join(',')}, symbol-table depth=#{context.symbol_table.depth}" )
193
320
  yield( "start", entryPoints ) if blk
194
321
 
195
322
  # collected during processing
@@ -203,16 +330,17 @@ module TlaParserS
203
330
 
204
331
  while TRUE do
205
332
 
206
- # done - if no mode 'unResolved'
333
+ # done - if no more in 'unResolved'
207
334
  break if unResolved.empty?
208
335
  # next to resolve
209
336
  entryPoint = unResolved.to_a.shift
210
- @logger.info( "#{__method__} next to resolve entryPoint #{entryPoint} " )
337
+ @logger.info( "#{__method__} next to resolve entryPoint #{entryPoint} " )
338
+ # already resolved
211
339
  next if resolved.include?( entryPoint )
212
340
 
213
341
  # array of hashes how entry point is resolved
214
342
  resolvedSymbols = resolveEntryPoint( entryPoint )
215
- @logger.info( "#{__method__} entryPoint #{entryPoint} ->resolvedSymbols=#{resolvedSymbols}" )
343
+ @logger.debug( "#{__method__} entryPoint #{entryPoint} ->resolvedSymbols=#{resolvedSymbols}" ) if @logger.debug?
216
344
 
217
345
  resolvedModules = # array of mod.names
218
346
  resolvedSymbols. # { :symbol=>, :resolved=> }
@@ -224,7 +352,7 @@ module TlaParserS
224
352
  map { |s| s[:module] } # extract moduleName
225
353
 
226
354
 
227
- @logger.debug( "#{__method__} resolvedModules=#{resolvedModules}" )
355
+ @logger.debug( "#{__method__} resolvedModules=#{resolvedModules}" ) if @logger.debug?
228
356
  newModules = resolvedModules.select { |m| !moduleNames.include?( m ) }
229
357
 
230
358
  # collect unresolved
@@ -238,7 +366,7 @@ module TlaParserS
238
366
 
239
367
  # return list of definitions (ie. newEntryPoint) in 'modules'
240
368
  newEntryPoints = entryPointsForModules( newModules )
241
- @logger.debug( "#{__method__} newEntryPoints=#{newEntryPoints}" )
369
+ @logger.debug( "#{__method__} newEntryPoints=#{newEntryPoints}" ) if @logger.debug?
242
370
 
243
371
  # one more resolved: move it to resolved
244
372
  unResolved = unResolved.delete( entryPoint )
@@ -246,7 +374,7 @@ module TlaParserS
246
374
 
247
375
  # still to resolve: unResolved + newEntryPoints - resolved
248
376
  unResolved = unResolved.union( newEntryPoints.to_set ).subtract( resolved )
249
- @logger.debug( "#{__method__} unResolved=#{unResolved}" )
377
+ @logger.debug( "#{__method__} unResolved=#{unResolved}" ) if @logger.debug?
250
378
 
251
379
  # collect modules
252
380
  moduleNames = moduleNames.union( newModules.to_set )
@@ -286,12 +414,18 @@ module TlaParserS
286
414
  # @return [Hash:Array] identifier in entry points
287
415
  def resolveEntryPoint( entryPoint )
288
416
  symbolTableEntry = context.resolveSymbol( entryPoint )
289
- @logger.debug( "#{__method__} symbolTableEntry #{entryPoint}-->#{symbolTableEntry}" )
417
+ @logger.debug( "#{__method__} symbolTableEntry #{entryPoint}-->#{symbolTableEntry}" ) if @logger.debug?
290
418
  if symbolTableEntry && symbolTableEntry[:resolved]
419
+ # return from cache
420
+ if symbolTableEntry[:cached]
421
+ return symbolTableEntry[:cached]
422
+ end
291
423
  tree = symbolTableEntry[:resolved][:tree]
292
- @logger.debug( "#{__method__} tree-->#{tree.inspect}" )
424
+ @logger.debug( "#{__method__} tree-->#{tree.inspect}" ) if @logger.debug?
293
425
  symbols = context.resolveDefine( tree )
294
- @logger.debug( "#{__method__} resolveDefine-->#{symbols.join('\n')}" )
426
+ @logger.debug( "#{__method__} resolveDefine-->#{symbols.join('\n')}" ) if @logger.debug?
427
+ symbolTableEntry[:cached] = symbols
428
+ symbols
295
429
  else
296
430
  msg = <<-EOS
297
431
  Unknown entrypoint '#{entryPoint}'
@@ -306,12 +440,24 @@ module TlaParserS
306
440
 
307
441
  end
308
442
 
443
+ # @!endgroup
444
+
445
+
446
+ # ------------------------------------------------------------------
447
+ # @!group private utilities
448
+
449
+
309
450
  # return list of definitions in 'modules'
310
451
  #
311
- # @param modules [String:Array] of module names to include
312
- # @return [String:Array] of entry points in the module
452
+ # @param modules [String:Array|String] of module names to include
453
+ #
454
+ # @return [String:Array] of entry points in 'modules'
455
+ #
313
456
  def entryPointsForModules( modules )
314
457
 
458
+ # ensure that it is an array
459
+ modules = modules.is_a?( Array ) ? modules : [modules]
460
+
315
461
  moduleSymbols = []
316
462
  modules.each do |moduleName|
317
463
  # resolve module entry poinsts
@@ -320,9 +466,11 @@ module TlaParserS
320
466
  moduleSymbols = moduleSymbols + moduleEntries
321
467
  end
322
468
  ret = moduleSymbols.map { |s| s[:symbol_name] }
323
- @logger.debug( "#{__method__} modules=#{modules.join(',')}-->ret=#{ret.join(',')}" )
469
+ @logger.debug( "#{__method__} modules=#{modules.join(',')}-->ret=#{ret.join(',')}" ) if @logger.debug?
324
470
  return ret
325
471
  end
472
+
473
+ # @!endgroup
326
474
 
327
475
  end
328
476
 
@@ -9,7 +9,7 @@ module TlaParserS
9
9
  # ------------------------------------------------------------------
10
10
  # Logger
11
11
 
12
- PROGNAME = "symbols" # progname for logger
12
+ PROGNAME = nil # progname for logger default class name
13
13
  include TlaParserS::Utils::MyLogger # mix logger
14
14
 
15
15
 
@@ -18,19 +18,29 @@ module TlaParserS
18
18
  def initialize( options={} )
19
19
 
20
20
  @logger = getLogger( PROGNAME, options )
21
- @logger.info( "#{__method__} initialized" )
21
+ @logger.info( "#{__method__} initialized symbol-table" )
22
22
 
23
23
  @stack = []
24
24
  end
25
25
 
26
+ def depth
27
+ @stack.length
28
+ end
29
+
26
30
  # Add a hash (with :symbol_type, :context_type, :context,
27
31
  # :symbol_name) to 'currentContext'
28
32
  #
29
33
  # @param entry [Hash] add hash to 'currentContext'
30
34
  def addEntry( entry )
31
35
  # get refeence to current contex
36
+ # do not ouput :tree property on info stack
37
+ @logger.info "#{__method__} @[#{@stack.length}] #{entry.inject({}){|e,(k,v)| e[k] = ( k == :tree ? true : v ); e } }"
38
+ @logger.debug "#{__method__} tree = #{entry[:tree].inspect}" if @logger.debug?
39
+ # @logger.debug "#{__method__} @[#{@stack.length}] #{entry}"
40
+
32
41
  context = currentContext
33
- context[entry[:symbol_name]] = entry
42
+ context[entry[:symbol_name]] = entry
43
+
34
44
  end
35
45
 
36
46
  # @param tree_node [SyntaxNode] node implementing
@@ -40,7 +50,7 @@ module TlaParserS
40
50
 
41
51
  if tree_node && tree_node.respond_to?(:symbol_definitions)
42
52
  tree_node.symbol_definitions.each do |symbol_definition|
43
- @logger.debug( "#{__method__} symbol_definition=#{symbol_definition}" )
53
+ @logger.debug( "#{__method__} moduleName #{moduleName}, symbol_definition=#{symbol_definition}" ) if @logger.debug?
44
54
  entry = {
45
55
  :context_type => tree_node.node_type,
46
56
  :context => tree_node.name,
@@ -50,7 +60,10 @@ module TlaParserS
50
60
  # optionally add moduleName
51
61
  entry[:module] = moduleName if moduleName
52
62
 
53
- # syntax tree defined only on procedure & macros
63
+ # output before tree
64
+ @logger.debug( "#{__method__} @#{stack.length} entry=#{entry}, parse tree=#{symbol_definition[:tree] ? 'given' : 'not given' } " ) if @logger.debug?
65
+
66
+ # syntax tree defined only on procedure & macros (and variable defs)
54
67
  entry[:tree] = symbol_definition[:tree] if symbol_definition[:tree]
55
68
 
56
69
  addEntry( entry )
@@ -93,6 +106,18 @@ module TlaParserS
93
106
  entries
94
107
  end
95
108
 
109
+ # @return [Hash:Array] of symbol table entries
110
+ def symbols
111
+ entries = {}
112
+ stack.reverse_each do |context|
113
+ # add key to entries unless it is already there
114
+ context.keys().each { |k| entries[k] = context[k] }
115
+ end
116
+ entries
117
+ end
118
+
119
+
120
+
96
121
  def resolveContext( identifier )
97
122
  stack.reverse_each do |context|
98
123
  # @logger.debug "context=#{context}, identifier=#{identifier}"
@@ -101,7 +126,14 @@ module TlaParserS
101
126
  return nil
102
127
  end
103
128
 
129
+
130
+ # Return entries in a module.
131
+ #
132
+ # Implementation iterates, in reverse, order symbol table stack,
133
+ # and searches for entries with :module == 'moduleName'.
134
+ #
104
135
  # @param moduleName [String] name of module to resolve
136
+ #
105
137
  # @return [Hash:Array] of symbol definition
106
138
  # {:context_type,:context,:symbol_type,:symbol_name} where
107
139
  # {:module} == 'moduleName'
@@ -111,11 +143,12 @@ module TlaParserS
111
143
  entries = []
112
144
  stack.reverse_each do |context|
113
145
  context.each do |symbol,entry|
114
- @logger.debug( "#{__method__} symbol=#{symbol}, entry=#{entry[:module]}" )
115
- entries << entry if entry[:module] == moduleName
146
+ @logger.debug( "#{__method__} symbol=#{symbol}, entry=#{entry[:module]}" ) if @logger.debug?
147
+ # entries << entry if entry[:module] == moduleName
148
+ entries << entry if !entry[:module].nil? && entry[:module].end_with?( moduleName )
116
149
  end
117
150
  end
118
- @logger.debug( "#{__method__} entries-->#{entries.join(',')}" )
151
+ @logger.debug( "#{__method__} entries-->#{entries.join(',')}" ) if @logger.debug?
119
152
  return entries
120
153
  end
121
154
 
@@ -123,10 +156,11 @@ module TlaParserS
123
156
  return stack.last
124
157
  end
125
158
 
126
- def dumpContext
159
+ def dumpContext( &blk )
127
160
  stack.reverse_each.with_index do |context, i|
128
- @logger.debug "Context #{i}"
129
- @logger.debug context.to_yaml
161
+ yield i, context if blk
162
+ @logger.debug "Context #{i}" if @logger.debug?
163
+ @logger.debug context.to_yaml if @logger.debug?
130
164
  end
131
165
  end
132
166