tla-parser-s 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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +75 -0
  3. data/VERSION +1 -0
  4. data/bin/tla-resolver.rb +7 -0
  5. data/lib/cli/cli.rb +90 -0
  6. data/lib/parser/exception.rb +6 -0
  7. data/lib/parser/lvalue.rb +63 -0
  8. data/lib/parser/parser.rb +129 -0
  9. data/lib/parser/parser_nodes.rb +1063 -0
  10. data/lib/parser/parser_sexp.treetop +442 -0
  11. data/lib/semantics/context.rb +355 -0
  12. data/lib/semantics/exception.rb +13 -0
  13. data/lib/semantics/resolver.rb +327 -0
  14. data/lib/semantics/symbol_table.rb +139 -0
  15. data/lib/tla-parser-s.rb +15 -0
  16. data/lib/utils/logger.rb +80 -0
  17. data/lib/utils/syntax_node.rb +73 -0
  18. data/lib/utils/version.rb +13 -0
  19. data/spec/fixtures/callables1.tla +64 -0
  20. data/spec/fixtures/directives.tla +7 -0
  21. data/spec/fixtures/resolver1/comments.tla +1 -0
  22. data/spec/fixtures/resolver1/directives.cfg +6 -0
  23. data/spec/fixtures/resolver1/directives.tla +12 -0
  24. data/spec/fixtures/resolver1/empty.tla +0 -0
  25. data/spec/fixtures/resolver1/macro1.tla +3 -0
  26. data/spec/fixtures/resolver1/macro2.tla +3 -0
  27. data/spec/fixtures/resolver1/op1.tla +3 -0
  28. data/spec/fixtures/resolver1/op2.tla +3 -0
  29. data/spec/fixtures/resolver1/proc1.tla +4 -0
  30. data/spec/fixtures/resolver1/proc2.tla +7 -0
  31. data/spec/fixtures/resolver1/proc3.tla +4 -0
  32. data/spec/fixtures/resolver1/proc4.tla +4 -0
  33. data/spec/fixtures/resolver1/proc4_hide.tla +4 -0
  34. data/spec/fixtures/resolver1/proc5.tla +4 -0
  35. data/spec/fixtures/resolver1/proc6.tla +4 -0
  36. data/spec/fixtures/resolver1/proc7.tla +4 -0
  37. data/spec/fixtures/resolver1/stmt_assert.tla +8 -0
  38. data/spec/fixtures/resolver1/stmt_assign.tla +6 -0
  39. data/spec/fixtures/resolver1/stmt_assign2.tla +6 -0
  40. data/spec/fixtures/resolver1/stmt_cond.tla +13 -0
  41. data/spec/fixtures/resolver1/stmt_either.tla +12 -0
  42. data/spec/fixtures/resolver1/stmt_print.tla +5 -0
  43. data/spec/fixtures/resolver1/var4.tla +1 -0
  44. data/spec/fixtures/resolver1/var5.tla +1 -0
  45. data/spec/fixtures/resolver1/var_choose_except.tla +2 -0
  46. data/spec/fixtures/resolver1/var_quantify.tla +4 -0
  47. data/spec/fixtures/resolver1/var_rec_except.tla +4 -0
  48. data/spec/fixtures/resolver1/var_rec_expr.tla +3 -0
  49. data/spec/fixtures/resolver1/var_record.tla +2 -0
  50. data/spec/fixtures/resolver1/var_seq.tla +1 -0
  51. data/spec/fixtures/resolver1/var_set.tla +1 -0
  52. data/spec/fixtures/resolver1/var_set_expr_map.tla +1 -0
  53. data/spec/fixtures/resolver1/var_x.tla +1 -0
  54. data/spec/fixtures/resolver1/variables.tla +4 -0
  55. data/spec/parser/parser_fixtures_spec.rb +117 -0
  56. data/spec/parser/parser_spec.rb +1649 -0
  57. data/spec/semantics/context_spec.rb +392 -0
  58. data/spec/semantics/resolver_spec.rb +364 -0
  59. data/spec/semantics/symbol_table_spec.rb +144 -0
  60. data/spec/spec_helper.rb +5 -0
  61. data/tla-parser-s.gemspec +41 -0
  62. metadata +153 -0
@@ -0,0 +1,13 @@
1
+ module TlaParserS
2
+
3
+ class ResolverException < Exception
4
+ end
5
+
6
+ class ContextException < Exception
7
+ end
8
+
9
+ class SymbolTableException < Exception
10
+ end
11
+
12
+
13
+ end
@@ -0,0 +1,327 @@
1
+ require 'set'
2
+
3
+ module TlaParserS
4
+
5
+ class Resolver
6
+
7
+ attr_reader :context # TlaParserS::Context
8
+ # create context to resolve
9
+ # references
10
+
11
+
12
+ attr_reader :parser # TlaParserS::Parser to
13
+ # parse TLA+ source code
14
+
15
+
16
+ attr_reader :report_unresolved # true if warn on unresolves
17
+
18
+ attr_reader :directives # [String:Array] of names
19
+ # for INVARIANTS and
20
+ # ASSUMPTIONS
21
+
22
+ attr_reader :directive_modules # array of module names containing
23
+ # directives
24
+
25
+ # ******************************************************************
26
+ # Load TLA+ snippets from file (or strings in unit test), and build
27
+ # global context to use in resolving.
28
+ #
29
+ # constructor:
30
+ # #initContext: add initContext
31
+ # #initSnippets: parse TLA+ snippets to context
32
+ # #resolveModules: resolve modules
33
+ #
34
+ # ******************************************************************
35
+
36
+
37
+ # ------------------------------------------------------------------
38
+ # Logger
39
+
40
+ PROGNAME = "resolver" # progname for logger
41
+ include TlaParserS::Utils::MyLogger # mix logger
42
+
43
+ # ------------------------------------------------------------------
44
+ # constructore
45
+ def initialize( options={} )
46
+
47
+ @logger = getLogger( PROGNAME, options )
48
+ @logger.info( "#{__method__} initialized" )
49
+
50
+ @report_unresolved = options[:report_unresolved]
51
+
52
+
53
+ @context = TlaParserS::Context.new( options )
54
+ @parser = TlaParserS::Parser.new( options )
55
+
56
+ # initiall no directives
57
+ @directives = []
58
+
59
+ # initially no directive modules
60
+ @directive_modules = []
61
+ end
62
+
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
78
+
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
93
+
94
+ # Build initial context (TLA+ reserved words, etc)
95
+ #
96
+ # @param names [String:Array] to put context
97
+ def initContext( names )
98
+
99
+ # create global context
100
+ context.initEntries(
101
+ names.map do |name|
102
+ {
103
+ :context_type => 'initalContext',
104
+ :context => "initalContext",
105
+ :symbol_type => "initalContext",
106
+ :symbol_name => name
107
+ }
108
+ end
109
+ )
110
+
111
+
112
+ end
113
+
114
+
115
+ # ------------------------------------------------------------------
116
+ # Load TLA+ snippets from file (or strings in unit test), and
117
+ # build global context to use in resolving.
118
+ #
119
+ # @param entries [ File:Array | String:Array] containg TLA+ snippets
120
+ #
121
+ # @param blk [Block] yields entry
122
+
123
+ def initSnippets( entries, &blk )
124
+
125
+ # create global context
126
+ context.pushContext
127
+
128
+ # iterate
129
+ entries && entries.each_with_index do |entry,i|
130
+
131
+ # skip directories
132
+ # next if entry.is_a?( File ) && File.directory?( entry )
133
+ next if File.directory?( entry )
134
+
135
+ # meaningfull modulename
136
+ moduleName = entry.is_a?( File ) ? entry.path : "entries[#{i}]"
137
+
138
+ @logger.info( "#{__method__} entry=#{entry}" )
139
+ begin
140
+
141
+ # String|File --> AST
142
+ snippets = parseSnippets( entry, moduleName )
143
+ yield( true, entry ) if blk
144
+
145
+ # collect directive defitions
146
+ addDirectives( snippets, moduleName )
147
+
148
+ # add to contex tree nodes, which respond to
149
+ # 'symbol_definitions'
150
+ context.addContext( snippets, moduleName )
151
+
152
+ rescue TlaParserS::ParseException => e
153
+
154
+ # quit if no block - or block decides to quit
155
+ if !blk || !yield( false, entry, e ) then
156
+ raise e
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ self
163
+ end
164
+
165
+ # ------------------------------------------------------------------
166
+ # Find modules 1) containing TLA+ snippets reachable from
167
+ # 'entryPoints', 2) modules reachabable for directive defintision,
168
+ # and 3) modules containing directive definitions
169
+ #
170
+ # @param entryPoints [String:Array] TLA+ definition names to implement
171
+ # @return [String:Array] names of modules needed in implementation
172
+ def resolveModules( entryPoints, &blk )
173
+ definitionModules = resolveModulesDo( entryPoints + directives, &blk )
174
+ return definitionModules + directive_modules
175
+ end
176
+
177
+
178
+ # ------------------------------------------------------------------
179
+ # Find modules containing TLA+ snippets needed to implement code
180
+ # for 'entryPoints'. Notice: A module may include additional
181
+ # entrypoint, which must also be satisfied.
182
+ #
183
+ # @param entryPoints [String:Array] TLA+ definition names to implement
184
+ #
185
+ # @param blk [Proc] called with (key, String:Array), where key \in
186
+ # {'start', 'resolved', 'unresolved'}, and String:Array list of
187
+ # symbols.
188
+ #
189
+ # @return [String:Arrays] of module names needed in implementation.
190
+ def resolveModulesDo( entryPoints, &blk )
191
+
192
+ @logger.info( "#{__method__} resolveModules for entryPoint=#{entryPoints.join(',')}" )
193
+ yield( "start", entryPoints ) if blk
194
+
195
+ # collected during processing
196
+ moduleNames = [].to_set
197
+
198
+ # collect here
199
+ unresolvedSymbols = []
200
+
201
+ unResolved = entryPoints.to_set
202
+ resolved = [].to_set
203
+
204
+ while TRUE do
205
+
206
+ # done - if no mode 'unResolved'
207
+ break if unResolved.empty?
208
+ # next to resolve
209
+ entryPoint = unResolved.to_a.shift
210
+
211
+ # array of hashes how entry point is resolved
212
+ resolvedSymbols = resolveEntryPoint( entryPoint )
213
+ @logger.debug( "#{__method__} entryPoint #{entryPoint} ->resolvedSymbols=#{resolvedSymbols}" )
214
+
215
+ resolvedModules = # array of mod.names
216
+ resolvedSymbols. # { :symbol=>, :resolved=> }
217
+ select { |e| e[:resolved] }. # successfully resolved
218
+ map { |e| e[:resolved] }. # discard :symbol =>.. :resolved => ...
219
+ select { |s| s[:module] }. # defines module ie. not
220
+ # TLA+ standar module, nor
221
+ # local context
222
+ map { |s| s[:module] } # extract moduleName
223
+
224
+
225
+ @logger.debug( "#{__method__} resolvedModules=#{resolvedModules}" )
226
+ newModules = resolvedModules.select { |m| !moduleNames.include?( m ) }
227
+
228
+ # collect unresolved
229
+ unresolvedSymbols +=
230
+ resolvedSymbols. # { :symbol=>, :resolved=> }
231
+ select { |r| r[:resolved].nil? }.# unresolved
232
+ map{ |r|
233
+ {
234
+ :symbol => r[:symbol],
235
+ :entry => entryPoint }} # add entry point causing error
236
+
237
+ # return list of definitions (ie. newEntryPoint) in 'modules'
238
+ newEntryPoints = entryPointsForModules( newModules )
239
+ @logger.debug( "#{__method__} newEntryPoints=#{newEntryPoints}" )
240
+
241
+ # one more resolved: move it to resolved
242
+ unResolved = unResolved.delete( entryPoint )
243
+ resolved = resolved.add( entryPoint )
244
+
245
+ # still to resolve: unResolved + newEntryPoints - resolved
246
+ unResolved = unResolved.union( newEntryPoints.to_set ).subtract( resolved )
247
+ @logger.debug( "#{__method__} unResolved=#{unResolved}" )
248
+
249
+ # collect modules
250
+ moduleNames = moduleNames.union( newModules.to_set )
251
+
252
+ end # looop for ever
253
+
254
+ # warn on output unresolved - if option set
255
+ reportUnresolved( unresolvedSymbols ) if report_unresolved
256
+ yield( "resolved", resolved.to_a ) if blk
257
+ yield( "unresolved", unresolvedSymbols ) if blk
258
+
259
+ @logger.info( "#{__method__} resolve-result ->#{moduleNames.to_a}" )
260
+ # set --> array
261
+ moduleNames.to_a
262
+
263
+ end # resolveModules
264
+
265
+ # Ouptput to stderr if 'unresolvedSymbols.any
266
+ #
267
+ # @param unresolvedSymbols [Hash:Array] of {:symbol,:entry} of unresolved symbols
268
+ #
269
+ def reportUnresolved( unresolvedSymbols )
270
+
271
+ warn <<-EOS if unresolvedSymbols.any?
272
+ Unresolved symbols:
273
+ -- #{unresolvedSymbols.map{ |u| 'Symbol \'' + u[:symbol] + '\' in entry \'' + u[:entry] + '\''}.join("\n -- ")}
274
+ EOS
275
+
276
+ end
277
+
278
+ # Return Hash:Array of symbols resolved in 'entryPoint'. First
279
+ # locates 'entryPoint' syntax tree in 'context', and then
280
+ # use 'resolveDefine' to resolve entries.
281
+ #
282
+ # @param entryPoint [String] name entry point to resolve
283
+ #
284
+ # @return [Hash:Array] identifier in entry points
285
+ def resolveEntryPoint( entryPoint )
286
+ symbolTableEntry = context.resolveSymbol( entryPoint )
287
+ @logger.debug( "#{__method__} symbolTableEntry #{entryPoint}-->#{symbolTableEntry}" )
288
+ if symbolTableEntry && symbolTableEntry[:resolved]
289
+ tree = symbolTableEntry[:resolved][:tree]
290
+ @logger.debug( "#{__method__} tree-->#{tree.inspect}" )
291
+ symbols = context.resolveDefine( tree )
292
+ @logger.debug( "#{__method__} resolveDefine-->#{symbols.join('\n')}" )
293
+ else
294
+ msg = <<-EOS
295
+ Unknown entrypoint '#{entryPoint}'
296
+
297
+ Known entry points: #{context.entries.join(',')}
298
+ EOS
299
+ @logger.error( "#{__method__} #{msg}" )
300
+ raise ResolverException.new msg
301
+ end
302
+
303
+ return [ symbolTableEntry ] + symbols
304
+
305
+ end
306
+
307
+ # return list of definitions in 'modules'
308
+ #
309
+ # @param modules [String:Array] of module names to include
310
+ # @return [String:Array] of entry points in the module
311
+ def entryPointsForModules( modules )
312
+
313
+ moduleSymbols = []
314
+ modules.each do |moduleName|
315
+ # resolve module entry poinsts
316
+ moduleEntries = context.resolveModule( moduleName )
317
+
318
+ moduleSymbols = moduleSymbols + moduleEntries
319
+ end
320
+ ret = moduleSymbols.map { |s| s[:symbol_name] }
321
+ @logger.debug( "#{__method__} modules=#{modules.join(',')}-->ret=#{ret.join(',')}" )
322
+ return ret
323
+ end
324
+
325
+ end
326
+
327
+ end
@@ -0,0 +1,139 @@
1
+ require 'yaml'
2
+
3
+ module TlaParserS
4
+
5
+ class SymbolTable
6
+
7
+ attr_reader :stack # Array as symbol table context
8
+
9
+ # ------------------------------------------------------------------
10
+ # Logger
11
+
12
+ PROGNAME = "symbols" # progname for logger
13
+ include TlaParserS::Utils::MyLogger # mix logger
14
+
15
+
16
+ # ------------------------------------------------------------------
17
+ # constructore
18
+ def initialize( options={} )
19
+
20
+ @logger = getLogger( PROGNAME, options )
21
+ @logger.info( "#{__method__} initialized" )
22
+
23
+ @stack = []
24
+ end
25
+
26
+ # Add a hash (with :symbol_type, :context_type, :context,
27
+ # :symbol_name) to 'currentContext'
28
+ #
29
+ # @param entry [Hash] add hash to 'currentContext'
30
+ def addEntry( entry )
31
+ # get refeence to current contex
32
+ context = currentContext
33
+ context[entry[:symbol_name]] = entry
34
+ end
35
+
36
+ # @param tree_node [SyntaxNode] node implementing
37
+ # `symbol_definitions` - method returning hash entries
38
+ # (:value,:node_type)
39
+ def addContext( tree_node, moduleName=nil )
40
+
41
+ if tree_node && tree_node.respond_to?(:symbol_definitions)
42
+ tree_node.symbol_definitions.each do |symbol_definition|
43
+ @logger.debug( "#{__method__} symbol_definition=#{symbol_definition}" )
44
+ entry = {
45
+ :context_type => tree_node.node_type,
46
+ :context => tree_node.name,
47
+ :symbol_type => symbol_definition[:node_type],
48
+ :symbol_name => symbol_definition[:value],
49
+ }
50
+ # optionally add moduleName
51
+ entry[:module] = moduleName if moduleName
52
+
53
+ # syntax tree defined only on procedure & macros
54
+ entry[:tree] = symbol_definition[:tree] if symbol_definition[:tree]
55
+
56
+ addEntry( entry )
57
+ end
58
+ end
59
+ end
60
+
61
+ # Create new context: add one more level to symbol table, add
62
+ # entries from 'tree_node.symbol_definitions'
63
+ #
64
+ # @param tree_node [SyntaxNode] define initial context content
65
+ def pushContext( tree_node, moduleName=nil )
66
+
67
+ context = {}
68
+ @stack.push( context )
69
+
70
+ addContext( tree_node, moduleName )
71
+ end
72
+
73
+ def popContext
74
+ if stack.length == 0 then
75
+ msg = "Stack underflow"
76
+ @logger.error( "#{__method__} #{msg}" )
77
+ raise SymbolTableException.new msg
78
+ end
79
+ @stack.pop
80
+ end
81
+
82
+ # @param level [int] number of levels to recurse (default 1)
83
+ # @return [String:Array] of entry names
84
+
85
+ def entries( level=1)
86
+ entries = []
87
+ stack.reverse_each do |context|
88
+ # add key to entries unless it is already there
89
+ context.keys().each { |k| entries << k unless entries.include?( k ) }
90
+ level -= 1
91
+ break if level <= 0
92
+ end
93
+ entries
94
+ end
95
+
96
+ def resolveContext( identifier )
97
+ stack.reverse_each do |context|
98
+ # @logger.debug "context=#{context}, identifier=#{identifier}"
99
+ return context[identifier] if context.has_key?( identifier )
100
+ end
101
+ return nil
102
+ end
103
+
104
+ # @param moduleName [String] name of module to resolve
105
+ # @return [Hash:Array] of symbol definition
106
+ # {:context_type,:context,:symbol_type,:symbol_name} where
107
+ # {:module} == 'moduleName'
108
+
109
+
110
+ def resolveModule( moduleName )
111
+ entries = []
112
+ stack.reverse_each do |context|
113
+ context.each do |symbol,entry|
114
+ @logger.debug( "#{__method__} symbol=#{symbol}, entry=#{entry[:module]}" )
115
+ entries << entry if entry[:module] == moduleName
116
+ end
117
+ end
118
+ @logger.debug( "#{__method__} entries-->#{entries.join(',')}" )
119
+ return entries
120
+ end
121
+
122
+ def currentContext
123
+ return stack.last
124
+ end
125
+
126
+ def dumpContext
127
+ stack.reverse_each.with_index do |context, i|
128
+ @logger.debug "Context #{i}"
129
+ @logger.debug context.to_yaml
130
+ end
131
+ end
132
+
133
+ def contextLevels
134
+ 0
135
+ end
136
+
137
+ end # class
138
+
139
+ end