rtext 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -14,3 +14,12 @@
14
14
  * Added result limit option to DefaultServiceProvider
15
15
  * Removed short_class_names option from Language
16
16
 
17
+ =0.4.0
18
+
19
+ * Made instantiator a lot more robust against parse errors
20
+ * Added DefaultLoader option to not reload fragments with errors
21
+ * Added service load progress indication and custom problem severity
22
+ * Fixed serialization of enum literals starting with a digit
23
+ * Fixed used port detection in service
24
+ * Fixed completion option order to match order defined in lanugage
25
+
@@ -188,13 +188,34 @@ The command implicitly reloads the model from the filesystem before checking for
188
188
  no parameters
189
189
 
190
190
  Response Format:
191
- result line n: <file name>
192
- result line n+1..n+m <line number>;<message>
191
+ result line 1: <file name>
192
+ result line 2..n <line number>;<message>
193
193
 
194
194
  Note that the file name is repeated only once for all problems within a file to reduce the amout of
195
195
  result data which needs to be transmitted.
196
196
 
197
197
 
198
+ ===Command: Show Problems 2
199
+
200
+ Since protocol version 1
201
+
202
+ Like the first version but provides progress information while the model is loaded and problem severity.
203
+
204
+ Request Format:
205
+ command id: "show_problems2"
206
+ no parameters
207
+
208
+ Response Format:
209
+ result line 1..n: progress:<[0..100]>
210
+ result line n+1: <file name>
211
+ result line n+2..m <[diwef]>;<line number>;<message>
212
+
213
+ Progress is expressed by a value between 0 and 100. The backend may choose to return any number
214
+ of progress lines. It should start with 0 and end with 100 and progress should not go backwards.
215
+
216
+ Problem severity is expressed with one of the following flags: d (debug), i (info), w (warn), e (error), f (fatal)
217
+
218
+
198
219
  ===Command: Reference Targets
199
220
 
200
221
  This command is used to retrieve the targets of a given reference. The reference location is expressed
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ DocFiles = [
8
8
 
9
9
  RTextGemSpec = Gem::Specification.new do |s|
10
10
  s.name = %q{rtext}
11
- s.version = "0.3.0"
11
+ s.version = "0.4.0"
12
12
  s.date = Time.now.strftime("%Y-%m-%d")
13
13
  s.summary = %q{Ruby Textual Modelling}
14
14
  s.email = %q{martin dot thiede at gmx de}
@@ -34,7 +34,7 @@ class Completer
34
34
  else
35
35
  # all target types which don't need a label
36
36
  # and all lables which are needed by a potential target type
37
- clazz.eAllReferences.select{|r| r.containment}.each do |r|
37
+ @lang.containments(clazz).each do |r|
38
38
  ([r.eType] + r.eType.eAllSubTypes).select{|t| !t.abstract}.each do |t|
39
39
  if @lang.containments_by_target_type(clazz, t).size > 1
40
40
  labled_refs << r
@@ -48,8 +48,7 @@ class Completer
48
48
  sort{|a,b| a.name <=> b.name}.collect do |c|
49
49
  class_completion_option(c)
50
50
  end +
51
- labled_refs.uniq.select{|r| r.name.index(context.prefix) == 0}.
52
- sort!{|a,b| a.name <=> b.name}.collect do |r|
51
+ labled_refs.uniq.select{|r| r.name.index(context.prefix) == 0}.collect do |r|
53
52
  CompletionOption.new("#{r.name}:", "<#{r.eType.name}>")
54
53
  end
55
54
  else
@@ -86,16 +85,14 @@ class Completer
86
85
  context.element.getGenericAsArray(f.name).size > 0}
87
86
  result += @lang.unlabled_arguments(clazz).
88
87
  select{|f| f.name.index(context.prefix) == 0 &&
89
- context.element.getGenericAsArray(f.name).empty?}[0..0].
90
- sort{|a,b| a.name <=> b.name}.collect do |f|
88
+ context.element.getGenericAsArray(f.name).empty?}[0..0].collect do |f|
91
89
  CompletionOption.new("<#{f.name}>", "<#{f.eType.name}>")
92
90
  end
93
91
  end
94
92
  # label completion
95
93
  result += @lang.labled_arguments(clazz).
96
94
  select{|f| f.name.index(context.prefix) == 0 &&
97
- context.element.getGenericAsArray(f.name).empty?}.
98
- sort{|a,b| a.name <=> b.name}.collect do |f|
95
+ context.element.getGenericAsArray(f.name).empty?}.collect do |f|
99
96
  CompletionOption.new("#{f.name}:", "<#{f.eType.name}>")
100
97
  end
101
98
  result
@@ -29,6 +29,10 @@ class DefaultLoader
29
29
  # :cache
30
30
  # a fragment cache to be used for loading
31
31
  #
32
+ # :dont_reload_with_errors
33
+ # if set to true, don't reload fragments which have parse errors
34
+ # instead keep the existing fragment but attach the new problem list
35
+ #
32
36
  def initialize(language, fragmented_model, options={})
33
37
  @lang = language
34
38
  @model = fragmented_model
@@ -40,6 +44,7 @@ class DefaultLoader
40
44
  @fragment_by_file = {}
41
45
  pattern = options[:pattern]
42
46
  @file_provider = options[:file_provider] || proc { Dir.glob(pattern) }
47
+ @dont_reload_with_errors = options[:dont_reload_with_errors]
43
48
  end
44
49
 
45
50
  # Loads or reloads model fragments from files using the file patterns or file provider
@@ -48,16 +53,19 @@ class DefaultLoader
48
53
  # :before_load
49
54
  # a proc which is called before a file is actually loaded, receives the fragment to load
50
55
  # into and a symbol indicating the kind of loading: :load, :load_cached, :load_update_cache
56
+ # optionally, the proc may take third argument which is the overall number of files
51
57
  # default: no before load proc
52
58
  #
53
59
  # :after_load
54
60
  # a proc which is called after a file has been loaded, receives the fragment loaded
61
+ # optionally, the proc may take second argument which is the overall number of files
55
62
  # default: no after load proc
56
63
  #
57
64
  def load(options={})
58
65
  @before_load_proc = options[:before_load]
59
66
  @after_load_proc = options[:after_load]
60
67
  files = @file_provider.call
68
+ @num_files = files.size
61
69
  @change_detector.check_files(files)
62
70
  @model.resolve(:fragment_provider => method(:fragment_provider),
63
71
  :use_target_type => @lang.per_type_identifier)
@@ -79,8 +87,18 @@ class DefaultLoader
79
87
  end
80
88
 
81
89
  def file_changed(file)
82
- file_removed(file)
83
- file_added(file)
90
+ fragment = RGen::Fragment::ModelFragment.new(file,
91
+ :identifier_provider => @lang.identifier_provider)
92
+ load_fragment_cached(fragment)
93
+ if @dont_reload_with_errors && fragment.data[:problems].size > 0
94
+ # keep old fragment but attach new problems
95
+ old_fragment = @fragment_by_file[file]
96
+ old_fragment.data[:problems] = fragment.data[:problems]
97
+ else
98
+ file_removed(file)
99
+ @model.add_fragment(fragment)
100
+ @fragment_by_file[file] = fragment
101
+ end
84
102
  end
85
103
 
86
104
  def fragment_provider(element)
@@ -101,18 +119,38 @@ class DefaultLoader
101
119
  end
102
120
  end
103
121
  if result == :invalid
104
- @before_load_proc && @before_load_proc.call(fragment, :load_update_cache)
122
+ call_before_load_proc(fragment, :load_update_cache)
105
123
  load_fragment(fragment)
106
124
  @cache.store(fragment)
107
- @after_load_proc && @after_load_proc.call(fragment)
125
+ call_after_load_proc(fragment)
108
126
  else
109
- @before_load_proc && @before_load_proc.call(fragment, :load_cached)
110
- @after_load_proc && @after_load_proc.call(fragment)
127
+ call_before_load_proc(fragment, :load_cached)
128
+ call_after_load_proc(fragment)
111
129
  end
112
130
  else
113
- @before_load_proc && @before_load_proc.call(fragment, :load)
131
+ call_before_load_proc(fragment, :load)
114
132
  load_fragment(fragment)
115
- @after_load_proc && @after_load_proc.call(fragment)
133
+ call_after_load_proc(fragment)
134
+ end
135
+ end
136
+
137
+ def call_before_load_proc(fragment, kind)
138
+ if @before_load_proc
139
+ if @before_load_proc.arity == 3
140
+ @before_load_proc.call(fragment, kind, @num_files)
141
+ else
142
+ @before_load_proc.call(fragment, kind)
143
+ end
144
+ end
145
+ end
146
+
147
+ def call_after_load_proc(fragment)
148
+ if @after_load_proc
149
+ if @after_load_proc.arity == 2
150
+ @after_load_proc.call(fragment, @num_files)
151
+ else
152
+ @after_load_proc.call(fragment)
153
+ end
116
154
  end
117
155
  end
118
156
 
@@ -13,8 +13,12 @@ class DefaultServiceProvider
13
13
  })
14
14
  end
15
15
 
16
- def load_model
17
- @loader.load
16
+ def load_model(options={})
17
+ if options[:on_progress]
18
+ @loader.load(:after_load => options[:on_progress])
19
+ else
20
+ @loader.load
21
+ end
18
22
  end
19
23
 
20
24
  ReferenceCompletionOption = Struct.new(:identifier, :type)
@@ -81,8 +85,8 @@ class DefaultServiceProvider
81
85
 
82
86
  FileProblems = Struct.new(:file, :problems)
83
87
  Problem = Struct.new(:severity, :line, :message)
84
- def get_problems
85
- load_model
88
+ def get_problems(options={})
89
+ load_model(options)
86
90
  result = []
87
91
  @model.fragments.sort{|a,b| a.location <=> b.location}.each do |fragment|
88
92
  problems = []
@@ -46,28 +46,27 @@ class Instantiator
46
46
  @fragment_ref = options[:fragment_ref]
47
47
  @context_class_stack = []
48
48
  parser = Parser.new(@lang.reference_regexp)
49
- begin
50
- @root_elements.clear
51
- parser.parse(str,
52
- :descent_visitor => lambda do |command|
53
- clazz = @lang.class_by_command(command.value, @context_class_stack.last)
54
- # in case no class is found, nil will be pushed, this will case the next command
55
- # lookup to act as if called from toplevel
56
- @context_class_stack.push(clazz)
57
- end,
58
- :ascent_visitor => lambda do |*args|
59
- if args[0]
60
- element =create_element(*args)
61
- @context_class_stack.pop
62
- element
63
- else
64
- unassociated_comments(args[3])
65
- end
66
- end)
67
- rescue Parser::Error => e
68
- problem(e.message, e.line)
69
- @unresolved_refs.clear if @unresolved_refs
70
- @root_elements.clear
49
+ @root_elements.clear
50
+ parser_problems = []
51
+ parser.parse(str,
52
+ :descent_visitor => lambda do |command|
53
+ clazz = @lang.class_by_command(command.value, @context_class_stack.last)
54
+ # in case no class is found, nil will be pushed, this will case the next command
55
+ # lookup to act as if called from toplevel
56
+ @context_class_stack.push(clazz)
57
+ end,
58
+ :ascent_visitor => lambda do |*args|
59
+ if args[0]
60
+ element =create_element(*args)
61
+ @context_class_stack.pop
62
+ element
63
+ else
64
+ unassociated_comments(args[3])
65
+ end
66
+ end,
67
+ :problems => parser_problems)
68
+ parser_problems.each do |p|
69
+ problem(p.message, p.line)
71
70
  end
72
71
  if @unresolved_refs
73
72
  @unresolved_refs.each do |ur|
@@ -115,7 +114,7 @@ class Instantiator
115
114
  if di_index < unlabled_args.size
116
115
  set_argument(element, unlabled_args[di_index], a, defined_args, command.line)
117
116
  di_index += 1
118
- else
117
+ elsif a != nil
119
118
  problem("Unexpected unlabled argument, #{unlabled_args.size} unlabled arguments expected", command.line)
120
119
  end
121
120
  end
@@ -203,6 +202,7 @@ class Instantiator
203
202
  return
204
203
  end
205
204
  value = [value] unless value.is_a?(Array)
205
+ value.compact!
206
206
  if value.size > 1 && !feature.many
207
207
  problem("Argument '#{name}' can take only one value", line)
208
208
  return
data/lib/rtext/parser.rb CHANGED
@@ -2,6 +2,8 @@ module RText
2
2
 
3
3
  class Parser
4
4
 
5
+ Problem = Struct.new(:message, :line)
6
+
5
7
  def initialize(reference_regexp)
6
8
  @reference_regexp = reference_regexp
7
9
  end
@@ -9,10 +11,17 @@ class Parser
9
11
  def parse(str, options)
10
12
  @dsc_visitor = options[:descent_visitor]
11
13
  @asc_visitor = options[:ascent_visitor]
14
+ @problems = options[:problems] || []
15
+ @non_consume_count = 0
16
+ @consume_problem_reported = false
12
17
  @tokens = tokenize(str, @reference_regexp)
13
18
  @last_line = @tokens.last && @tokens.last.line
14
- while next_token
15
- parse_statement(true, true)
19
+ #@debug = true
20
+ begin
21
+ while next_token
22
+ statement = parse_statement(true, true)
23
+ end
24
+ rescue InternalError
16
25
  end
17
26
  end
18
27
 
@@ -23,17 +32,22 @@ class Parser
23
32
  if (next_token && next_token == :identifier) || !allow_unassociated_comment
24
33
  comments << [ comment, :above] if comment
25
34
  command = consume(:identifier)
26
- @dsc_visitor.call(command)
27
- arg_list = []
28
- parse_argument_list(arg_list)
29
- element_list = []
30
- if next_token == "{"
31
- parse_statement_block(element_list, comments)
35
+ if command
36
+ @dsc_visitor.call(command)
37
+ arg_list = []
38
+ parse_argument_list(arg_list)
39
+ element_list = []
40
+ if next_token == "{"
41
+ parse_statement_block(element_list, comments)
42
+ end
43
+ eol_comment = parse_eol_comment
44
+ comments << [ eol_comment, :eol ] if eol_comment
45
+ consume(:newline)
46
+ @asc_visitor.call(command, arg_list, element_list, comments, is_root)
47
+ else
48
+ discard_until(:newline)
49
+ nil
32
50
  end
33
- eol_comment = parse_eol_comment
34
- comments << [ eol_comment, :eol ] if eol_comment
35
- consume(:newline)
36
- @asc_visitor.call(command, arg_list, element_list, comments, is_root)
37
51
  elsif comment
38
52
  # if there is no statement, the comment is non-optional
39
53
  comments << [ comment, :unassociated ]
@@ -42,6 +56,8 @@ class Parser
42
56
  else
43
57
  # die expecting an identifier (next token is not an identifier)
44
58
  consume(:identifier)
59
+ discard_until(:newline)
60
+ nil
45
61
  end
46
62
  end
47
63
 
@@ -90,7 +106,8 @@ class Parser
90
106
  else
91
107
  eol_comment = parse_eol_comment
92
108
  comments << [ eol_comment, :eol ] if eol_comment
93
- consume(:newline)
109
+ nl = consume(:newline)
110
+ discard_until(:newline) unless nl
94
111
  parse_statement
95
112
  end
96
113
  end
@@ -99,7 +116,8 @@ class Parser
99
116
  consume("[")
100
117
  eol_comment = parse_eol_comment
101
118
  comments << [ eol_comment, :eol ] if eol_comment
102
- consume(:newline)
119
+ nl = consume(:newline)
120
+ discard_until(:newline) unless nl
103
121
  result = []
104
122
  while next_token && next_token != "]"
105
123
  statement = parse_statement(false, true)
@@ -114,7 +132,7 @@ class Parser
114
132
 
115
133
  def parse_argument_list(arg_list)
116
134
  first = true
117
- while !["{", :comment, :newline].include?(next_token)
135
+ while (AnyValue + [",", "[", :label, :error]).include?(next_token)
118
136
  consume(",") unless first
119
137
  first = false
120
138
  parse_argument(arg_list)
@@ -142,7 +160,7 @@ class Parser
142
160
  consume("[")
143
161
  first = true
144
162
  result = []
145
- while next_token != "]"
163
+ while (AnyValue + [",", :error]).include?(next_token)
146
164
  consume(",") unless first
147
165
  first = false
148
166
  result << parse_value
@@ -151,34 +169,71 @@ class Parser
151
169
  result
152
170
  end
153
171
 
172
+ AnyValue = [:identifier, :integer, :float, :string, :boolean, :reference]
173
+
154
174
  def parse_value
155
- consume(:identifier, :integer, :float, :string, :boolean, :reference)
175
+ consume(*AnyValue)
156
176
  end
157
177
 
158
178
  def next_token
159
179
  @tokens.first && @tokens.first.kind
160
180
  end
161
181
 
162
- class Error < Exception
163
- attr_reader :message, :line
164
- def initialize(message, line)
165
- @message, @line = message, line
182
+ def discard_until(kind)
183
+ t = @tokens.shift
184
+ if t
185
+ puts "discarding #{t.kind} #{t.value}" if @debug
186
+ while t.kind != kind
187
+ t = @tokens.shift
188
+ break unless t
189
+ puts "discarding #{t.kind} #{t.value}" if @debug
190
+ end
166
191
  end
167
192
  end
168
193
 
169
194
  def consume(*args)
170
- t = @tokens.shift
195
+ t = @tokens.first
171
196
  if t.nil?
172
- raise Error.new("Unexpected end of file, expected #{args.join(", ")}", @last_line)
197
+ @non_consume_count += 1
198
+ report_consume_problem("Unexpected end of file, expected #{args.join(", ")}", @last_line)
199
+ return nil
173
200
  end
174
201
  if args.include?(t.kind)
202
+ @tokens.shift
203
+ @consume_problem_reported = false
204
+ @non_consume_count = 0
205
+ puts "consuming #{t.kind} #{t.value}" if @debug
175
206
  t
176
207
  else
177
208
  if t.kind == :error
178
- raise Error.new("Parse error on token '#{t.value}'", t.line)
209
+ @tokens.shift
210
+ @non_consume_count = 0
211
+ report_consume_problem("Parse error on token '#{t.value}'", t.line)
212
+ return nil
179
213
  else
180
214
  value = " '#{t.value}'" if t.value
181
- raise Error.new("Unexpected #{t.kind}#{value}, expected #{args.join(", ")}", t.line)
215
+ @non_consume_count += 1
216
+ report_consume_problem("Unexpected #{t.kind}#{value}, expected #{args.join(", ")}", t.line)
217
+ return nil
218
+ end
219
+ end
220
+ end
221
+
222
+ class InternalError < Exception
223
+ end
224
+
225
+ def report_consume_problem(message, line)
226
+ problem = Problem.new(message, line)
227
+ if @non_consume_count > 100
228
+ # safety check, stop reoccuring problems to avoid endless loops
229
+ @problems << Problem.new("Internal error", line)
230
+ puts [@problems.last.message, @problems.last.line].inspect if @debug
231
+ raise InternalError.new
232
+ else
233
+ if !@consume_problem_reported
234
+ @consume_problem_reported = true
235
+ @problems << problem
236
+ puts [@problems.last.message, @problems.last.line].inspect if @debug
182
237
  end
183
238
  end
184
239
  end
@@ -133,7 +133,7 @@ class Serializer
133
133
  result << v.to_s
134
134
  end
135
135
  elsif feature.eType.is_a?(RGen::ECore::EEnum)
136
- if v.to_s =~ /\W/
136
+ if v.to_s =~ /^\d|\W/
137
137
  result << "\"#{v.to_s.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
138
138
  gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
139
139
  else
data/lib/rtext/service.rb CHANGED
@@ -54,13 +54,23 @@ class Service
54
54
  cmd = lines.shift
55
55
  invocation_id = lines.shift
56
56
  response = nil
57
+ progress_index = 0
57
58
  case cmd
59
+ when "protocol_version"
60
+ response = ["1"]
58
61
  when "refresh"
59
62
  response = refresh(lines)
60
63
  when "complete"
61
64
  response = complete(lines)
62
65
  when "show_problems"
63
66
  response = get_problems(lines)
67
+ when "show_problems2"
68
+ response = get_problems(lines, :with_severity => true, :on_progress => lambda do |frag, num_frags|
69
+ progress_index += 1
70
+ num_frags = 1 if num_frags < 1
71
+ progress = ["progress: #{progress_index*100/num_frags}"]
72
+ send_response(progress, invocation_id, socket, from, :incremental => true)
73
+ end)
64
74
  when "get_reference_targets"
65
75
  response = get_reference_targets(lines)
66
76
  when "get_elements"
@@ -79,7 +89,7 @@ class Service
79
89
 
80
90
  private
81
91
 
82
- def send_response(response, invocation_id, socket, from)
92
+ def send_response(response, invocation_id, socket, from, options={})
83
93
  @logger.debug(response.inspect) if @logger
84
94
  loop do
85
95
  packet_lines = []
@@ -88,7 +98,7 @@ class Service
88
98
  size += response.first.size
89
99
  packet_lines << response.shift
90
100
  end
91
- if response.size > 0
101
+ if options[:incremental] || response.size > 0
92
102
  packet_lines.unshift("more\n")
93
103
  else
94
104
  packet_lines.unshift("last\n")
@@ -104,7 +114,7 @@ class Service
104
114
  port = PortRangeStart
105
115
  begin
106
116
  socket.bind("localhost", port)
107
- rescue Errno::EADDRINUSE
117
+ rescue Errno::EADDRINUSE, Errno::EAFNOSUPPORT
108
118
  port += 1
109
119
  retry if port <= PortRangeEnd
110
120
  raise
@@ -120,7 +130,7 @@ class Service
120
130
  def complete(lines)
121
131
  linepos = lines.shift.to_i
122
132
  context = ContextBuilder.build_context(@lang, lines, linepos)
123
- @logger.debug("context element: #{@lang.identifier_provider.call(context.element, nil)}") if @logger
133
+ @logger.debug("context element: #{@lang.identifier_provider.call(context.element, nil)}") if context && @logger
124
134
  current_line = lines.pop
125
135
  current_line ||= ""
126
136
  options = @completer.complete(context, lambda {|ref|
@@ -132,13 +142,13 @@ class Service
132
142
  }
133
143
  end
134
144
 
135
- def get_problems(lines)
136
- # TODO: severity
145
+ def get_problems(lines, options={})
137
146
  result = []
138
- @service_provider.get_problems.each do |fp|
147
+ severity = options[:with_severity] ? "e;" : ""
148
+ @service_provider.get_problems(:on_progress => options[:on_progress]).each do |fp|
139
149
  result << fp.file+"\n"
140
150
  fp.problems.each do |p|
141
- result << "#{p.line};#{p.message}\n"
151
+ result << "#{severity}#{p.line};#{p.message}\n"
142
152
  end
143
153
  end
144
154
  result
@@ -41,10 +41,10 @@ def test_after_command
41
41
  END
42
42
  assert_options([
43
43
  ["<unlabled1>", "<EString>"],
44
+ ["text:", "<EString>"],
44
45
  ["nums:", "<EInt>"],
45
- ["others:", "<TestNode>"],
46
46
  ["related:", "<TestNode>"],
47
- ["text:", "<EString>"]
47
+ ["others:", "<TestNode>"]
48
48
  ], options)
49
49
  end
50
50
 
@@ -71,9 +71,9 @@ def test_after_labled_value
71
71
  TestNode nums: 1, |
72
72
  END
73
73
  assert_options([
74
- ["others:", "<TestNode>"],
74
+ ["text:", "<EString>"],
75
75
  ["related:", "<TestNode>"],
76
- ["text:", "<EString>"]
76
+ ["others:", "<TestNode>"]
77
77
  ], options)
78
78
  end
79
79
 
@@ -83,10 +83,10 @@ def test_after_unlabled_value
83
83
  END
84
84
  assert_options([
85
85
  ["<unlabled2>", "<EInt>"],
86
+ ["text:", "<EString>"],
86
87
  ["nums:", "<EInt>"],
87
- ["others:", "<TestNode>"],
88
88
  ["related:", "<TestNode>"],
89
- ["text:", "<EString>"]
89
+ ["others:", "<TestNode>"]
90
90
  ], options)
91
91
  end
92
92
 
@@ -95,10 +95,10 @@ def test_after_unlabled_value2
95
95
  TestNode "bla", 1, |
96
96
  END
97
97
  assert_options([
98
+ ["text:", "<EString>"],
98
99
  ["nums:", "<EInt>"],
99
- ["others:", "<TestNode>"],
100
100
  ["related:", "<TestNode>"],
101
- ["text:", "<EString>"]
101
+ ["others:", "<TestNode>"]
102
102
  ], options)
103
103
  end
104
104
 
@@ -107,9 +107,9 @@ def test_after_array
107
107
  TestNode nums: [1, 2], |
108
108
  END
109
109
  assert_options([
110
- ["others:", "<TestNode>"],
110
+ ["text:", "<EString>"],
111
111
  ["related:", "<TestNode>"],
112
- ["text:", "<EString>"]
112
+ ["others:", "<TestNode>"]
113
113
  ], options)
114
114
  end
115
115
 
@@ -12,7 +12,7 @@ class InstantiatorTest < Test::Unit::TestCase
12
12
  module TestMM
13
13
  extend RGen::MetamodelBuilder::ModuleExtension
14
14
  class TestNode < RGen::MetamodelBuilder::MMBase
15
- SomeEnum = RGen::MetamodelBuilder::DataTypes::Enum.new([:A, :B, :'non-word*chars'])
15
+ SomeEnum = RGen::MetamodelBuilder::DataTypes::Enum.new([:A, :B, :'non-word*chars', :'2you'])
16
16
  has_attr 'text', String
17
17
  has_attr 'integer', Integer
18
18
  has_attr 'boolean', Boolean
@@ -199,7 +199,7 @@ class InstantiatorTest < Test::Unit::TestCase
199
199
  TestNode text: B
200
200
  TestNode a problem here
201
201
  ), TestMM, :file_name => "some_file")
202
- assert_equal ["some_file"], problems.collect{|p| p.file}
202
+ assert_equal "some_file", problems.first.file
203
203
  end
204
204
 
205
205
  def test_file_name_setter
@@ -323,6 +323,19 @@ class InstantiatorTest < Test::Unit::TestCase
323
323
  assert_model_simple(env)
324
324
  end
325
325
 
326
+ def test_no_newline_at_eof
327
+ env, problems = instantiate(%Q(
328
+ TestNode), TestMM)
329
+ assert_no_problems(problems)
330
+ end
331
+
332
+ def test_no_newline_at_eof2
333
+ env, problems = instantiate(%Q(
334
+ TestNode {
335
+ }), TestMM)
336
+ assert_no_problems(problems)
337
+ end
338
+
326
339
  #
327
340
  # references
328
341
  #
@@ -585,7 +598,7 @@ class InstantiatorTest < Test::Unit::TestCase
585
598
  }
586
599
  ), TestMM2)
587
600
  assert_problems([
588
- /unexpected }, expected identifier/i
601
+ /unexpected \}, expected identifier/i
589
602
  ], problems)
590
603
  end
591
604
 
@@ -707,6 +720,351 @@ class InstantiatorTest < Test::Unit::TestCase
707
720
  assert_problems([/command 'NonRootClass' can not be used on root level/i], problems)
708
721
  end
709
722
 
723
+ #
724
+ # problem recovery
725
+ #
726
+
727
+ def test_missing_value
728
+ root_elements = []
729
+ env, problems = instantiate(%Q(
730
+ TestNode nums: 1, text:
731
+ TestNode nums: 2, text: {
732
+ SubNode
733
+ }
734
+ TestNode text: ,nums: 3 {
735
+ SubNode
736
+ }
737
+ TestNode nums: , text: , bla:
738
+ ), TestMM, :root_elements => root_elements)
739
+ assert_equal 4, root_elements.size
740
+ assert_equal [1], root_elements[0].nums
741
+ assert_nil root_elements[0].text
742
+ assert_equal [2], root_elements[1].nums
743
+ assert_equal 1, root_elements[1].childs.size
744
+ assert_equal [3], root_elements[2].nums
745
+ assert_equal 1, root_elements[2].childs.size
746
+ assert_problems([
747
+ [/unexpected newline, expected.*integer/i, 2],
748
+ [/unexpected \{, expected.*integer/i, 3],
749
+ [/unexpected ,, expected.*integer/i, 6],
750
+ [/unexpected ,, expected.*integer/i, 9],
751
+ [/unexpected ,, expected.*integer/i, 9],
752
+ [/unexpected newline, expected.*integer/i, 9],
753
+ [/unknown argument 'bla'/i, 9],
754
+ ], problems)
755
+ end
756
+
757
+ def test_missing_comma
758
+ root_elements = []
759
+ env, problems = instantiate(%Q(
760
+ TestNode nums: 1 text: "bla"
761
+ ), TestMM, :root_elements => root_elements)
762
+ assert_equal 1, root_elements.size
763
+ assert_equal [1], root_elements[0].nums
764
+ assert_equal "bla", root_elements[0].text
765
+ assert_problems([
766
+ /unexpected label .*, expected ,/i,
767
+ ], problems)
768
+ end
769
+
770
+ def test_missing_label
771
+ root_elements = []
772
+ env, problems = instantiate(%Q(
773
+ TestNode nums: 1 "bla"
774
+ ), TestMM, :root_elements => root_elements)
775
+ assert_equal 1, root_elements.size
776
+ assert_equal [1], root_elements[0].nums
777
+ assert_problems([
778
+ /unexpected string 'bla', expected ,/i,
779
+ /unexpected unlabled argument/i
780
+ ], problems)
781
+ end
782
+
783
+ def test_unclosed_bracket
784
+ root_elements = []
785
+ env, problems = instantiate(%Q(
786
+ TestNode nums: [1, "bla"
787
+ TestNode nums: [1, text: "bla"
788
+ TestNode nums: [1 text: "bla"
789
+ TestNode nums: [1 "bla"
790
+ TestNode [1, "bla"
791
+ TestNode [1, "bla" [
792
+ TestNode [1, "bla", [
793
+ ), TestMM, :root_elements => root_elements)
794
+ assert_equal 7, root_elements.size
795
+ assert_equal [1], root_elements[0].nums
796
+ assert_nil root_elements[0].text
797
+ assert_equal [1], root_elements[1].nums
798
+ assert_equal "bla", root_elements[1].text
799
+ assert_equal [1], root_elements[2].nums
800
+ assert_equal "bla", root_elements[2].text
801
+ assert_equal [1], root_elements[3].nums
802
+ assert_nil root_elements[3].text
803
+ assert_equal [], root_elements[4].nums
804
+ assert_nil root_elements[4].text
805
+ assert_problems([
806
+ [/unexpected newline, expected \]/i, 2],
807
+ [/argument 'nums' can not take a string, expected integer/i, 2],
808
+ [/unexpected label 'text', expected identifier/i, 3],
809
+ [/unexpected label 'text', expected \]/i, 4],
810
+ [/unexpected string 'bla', expected ,/i, 5],
811
+ [/argument 'nums' can not take a string, expected integer/i, 5],
812
+ [/unexpected newline, expected \]/i, 5],
813
+ [/unexpected newline, expected \]/i, 6],
814
+ [/unexpected unlabled argument/i, 6],
815
+ [/unexpected \[, expected \]/i, 7],
816
+ [/unexpected newline, expected \]/i, 7],
817
+ [/unexpected unlabled argument/i, 7],
818
+ [/unexpected unlabled argument/i, 7],
819
+ [/unexpected \[, expected identifier/i, 8],
820
+ [/unexpected unlabled argument/i, 8],
821
+ [/unexpected unlabled argument/i, 8],
822
+ [/unexpected newline, expected \]/i, 8],
823
+ ], problems)
824
+ end
825
+
826
+ def test_closing_bracket
827
+ root_elements = []
828
+ env, problems = instantiate(%Q(
829
+ TestNode ]
830
+ TestNode 1 ]
831
+ TestNode 1, ]
832
+ TestNode nums: ]1, "bla"
833
+ TestNode text: "bla" ]
834
+ ), TestMM, :root_elements => root_elements)
835
+ assert_equal 5, root_elements.size
836
+ assert_equal [], root_elements[3].nums
837
+ assert_equal "bla", root_elements[4].text
838
+ assert_problems([
839
+ [/unexpected \], expected newline/i, 2],
840
+ [/unexpected \], expected newline/i, 3],
841
+ [/unexpected unlabled argument/i, 3],
842
+ [/unexpected \], expected identifier/i, 4],
843
+ [/unexpected unlabled argument/i, 4],
844
+ [/unexpected \], expected identifier/i, 5],
845
+ [/unexpected \], expected newline/i, 6],
846
+ ], problems)
847
+ end
848
+
849
+ def test_closing_brace
850
+ root_elements = []
851
+ env, problems = instantiate(%Q(
852
+ TestNode }
853
+ TestNode 1 }
854
+ TestNode 1, }
855
+ TestNode nums: }1, "bla"
856
+ TestNode text: "bla" }
857
+ ), TestMM, :root_elements => root_elements)
858
+ assert_equal 5, root_elements.size
859
+ assert_equal [], root_elements[3].nums
860
+ assert_equal "bla", root_elements[4].text
861
+ assert_problems([
862
+ [/unexpected \}, expected newline/i, 2],
863
+ [/unexpected \}, expected newline/i, 3],
864
+ [/unexpected unlabled argument/i, 3],
865
+ [/unexpected \}, expected identifier/i, 4],
866
+ [/unexpected unlabled argument/i, 4],
867
+ [/unexpected \}, expected identifier/i, 5],
868
+ [/unexpected \}, expected newline/i, 6],
869
+ ], problems)
870
+ end
871
+
872
+ def test_starting_non_command
873
+ root_elements = []
874
+ env, problems = instantiate(%Q(
875
+ \)
876
+ TestNode
877
+ *
878
+ TestNode
879
+ $
880
+ TestNode
881
+ ,
882
+ TestNode
883
+ [
884
+ TestNode
885
+ {
886
+ TestNode
887
+ ]
888
+ TestNode
889
+ }
890
+ TestNode
891
+ }}
892
+ ), TestMM, :root_elements => root_elements)
893
+ assert_equal 8, root_elements.size
894
+ assert_problems([
895
+ [/parse error on token '\)'/i, 2],
896
+ [/parse error on token '\*'/i, 4],
897
+ [/parse error on token '\$'/i, 6],
898
+ [/unexpected ,, expected identifier/i, 8],
899
+ [/unexpected \[, expected identifier/i, 10],
900
+ [/unexpected \{, expected identifier/i, 12],
901
+ [/unexpected \], expected identifier/i, 14],
902
+ [/unexpected \}, expected identifier/i, 16],
903
+ [/unexpected \}, expected identifier/i, 18],
904
+ ], problems)
905
+ end
906
+
907
+ def test_parse_error_in_argument_list
908
+ root_elements = []
909
+ env, problems = instantiate(%Q(
910
+ TestNode text: "bla", * nums: 1
911
+ TestNode text: "bla" * , nums: 1
912
+ TestNode ?text: "bla"
913
+ TestNode nums: [1, * 3]
914
+ ), TestMM, :root_elements => root_elements)
915
+ assert_equal 4, root_elements.size
916
+ assert_equal "bla", root_elements[0].text
917
+ assert_equal [1], root_elements[0].nums
918
+ assert_equal "bla", root_elements[1].text
919
+ assert_equal [1], root_elements[1].nums
920
+ assert_equal [1, 3], root_elements[3].nums
921
+ assert_problems([
922
+ [/parse error on token '\*'/i, 2],
923
+ [/parse error on token '\*'/i, 3],
924
+ [/parse error on token '\?text:'/i, 4],
925
+ [/unexpected unlabled argument/i, 4],
926
+ [/parse error on token '\*'/i, 5],
927
+ ], problems)
928
+ end
929
+
930
+ def test_unclosed_brace
931
+ root_elements = []
932
+ env, problems = instantiate(%Q(
933
+ TestNode {
934
+ ), TestMM, :root_elements => root_elements)
935
+ assert_equal 1, root_elements.size
936
+ assert_problems([
937
+ [/unexpected end of file, expected \}/i, 2]
938
+ ], problems)
939
+ end
940
+
941
+ def test_unclosed_brace2
942
+ root_elements = []
943
+ env, problems = instantiate(%Q(
944
+ TestNode {
945
+ *
946
+ ), TestMM, :root_elements => root_elements)
947
+ assert_equal 1, root_elements.size
948
+ assert_problems([
949
+ [/parse error on token '\*'/i, 3]
950
+ ], problems)
951
+ end
952
+
953
+ def test_unclosed_brace3
954
+ root_elements = []
955
+ env, problems = instantiate(%Q(
956
+ TestNode {
957
+ childs:
958
+ ), TestMM, :root_elements => root_elements)
959
+ assert_equal 1, root_elements.size
960
+ assert_problems([
961
+ [/unexpected end of file, expected identifier/i, 3]
962
+ ], problems)
963
+ end
964
+
965
+ def test_label_without_child
966
+ root_elements = []
967
+ env, problems = instantiate(%Q(
968
+ TestNode {
969
+ childs:
970
+ }
971
+ ), TestMM, :root_elements => root_elements)
972
+ assert_equal 1, root_elements.size
973
+ assert_problems([
974
+ [/unexpected \}, expected identifier/i, 4]
975
+ ], problems)
976
+ end
977
+
978
+ def test_unclosed_bracket
979
+ root_elements = []
980
+ env, problems = instantiate(%Q(
981
+ TestNode {
982
+ childs: [
983
+ ), TestMM, :root_elements => root_elements)
984
+ assert_equal 1, root_elements.size
985
+ assert_problems([
986
+ [/unexpected end of file, expected \]/i, 3]
987
+ ], problems)
988
+ end
989
+
990
+ def test_child_label_problems
991
+ root_elements = []
992
+ env, problems = instantiate(%Q(
993
+ TestNode {
994
+ childs: x
995
+ SubNode
996
+ childs: *
997
+ SubNode
998
+ childs: &
999
+ }
1000
+ ), TestMM, :root_elements => root_elements)
1001
+ assert_equal 1, root_elements.size
1002
+ assert_equal 2, root_elements[0].childs.size
1003
+ assert_problems([
1004
+ [/unexpected identifier 'x', expected newline/i, 3],
1005
+ [/parse error on token '\*'/i, 5],
1006
+ [/parse error on token '&'/i, 7]
1007
+ ], problems)
1008
+ end
1009
+
1010
+ def test_child_label_problems_with_bracket
1011
+ root_elements = []
1012
+ env, problems = instantiate(%Q(
1013
+ TestNode {
1014
+ childs: [ x
1015
+ SubNode
1016
+ ]
1017
+ childs: [ *
1018
+ SubNode
1019
+ ]
1020
+ childs: [&
1021
+ ]
1022
+ }
1023
+ ), TestMM, :root_elements => root_elements)
1024
+ assert_equal 1, root_elements.size
1025
+ assert_equal 2, root_elements[0].childs.size
1026
+ assert_problems([
1027
+ [/unexpected identifier 'x', expected newline/i, 3],
1028
+ [/parse error on token '\*'/i, 6],
1029
+ [/parse error on token '&'/i, 9]
1030
+ ], problems)
1031
+ end
1032
+
1033
+ def test_missing_closing_bracket
1034
+ root_elements = []
1035
+ env, problems = instantiate(%Q(
1036
+ TestNode {
1037
+ childs: [
1038
+ SubNode
1039
+ childs: [
1040
+ SubNode
1041
+ SubNode
1042
+ }
1043
+ ), TestMM, :root_elements => root_elements)
1044
+ assert_equal 1, root_elements.size
1045
+ assert_equal 3, root_elements[0].childs.size
1046
+ assert_problems([
1047
+ [/unexpected label 'childs', expected identifier/i, 5],
1048
+ [/unexpected \}, expected identifier/i, 8],
1049
+ ], problems)
1050
+ end
1051
+
1052
+ def test_missing_closing_brace
1053
+ root_elements = []
1054
+ env, problems = instantiate(%Q(
1055
+ TestNode {
1056
+ TestNode {
1057
+ TestNode
1058
+ }
1059
+ ), TestMM, :root_elements => root_elements)
1060
+ assert_equal 1, root_elements.size
1061
+ assert_equal 1, root_elements[0].childs.size
1062
+ assert_equal 1, root_elements[0].childs[0].childs.size
1063
+ assert_problems([
1064
+ [/unexpected end of file, expected \}/i, 5],
1065
+ ], problems)
1066
+ end
1067
+
710
1068
  #
711
1069
  # command name provider
712
1070
  #
@@ -940,10 +1298,11 @@ class InstantiatorTest < Test::Unit::TestCase
940
1298
  TestNode enum: A
941
1299
  TestNode enum: B
942
1300
  TestNode enum: "non-word*chars"
1301
+ TestNode enum: "2you"
943
1302
  }
944
1303
  ), TestMM)
945
1304
  assert_no_problems(problems)
946
- assert_equal [:A, :B, :'non-word*chars'], env.find(:text => "root").first.childs.collect{|c| c.enum}
1305
+ assert_equal [:A, :B, :'non-word*chars', :'2you'], env.find(:text => "root").first.childs.collect{|c| c.enum}
947
1306
  end
948
1307
 
949
1308
  #
@@ -975,7 +1334,7 @@ class InstantiatorTest < Test::Unit::TestCase
975
1334
  c.name == "TestNode" || c.name == "Data" || c.name == "TestNodeSub" || c.name == "SubNode"}))
976
1335
  inst = RText::Instantiator.new(lang)
977
1336
  problems = []
978
- inst.instantiate(text, options.merge({:env => env, :problems => problems}))
1337
+ inst.instantiate(text, options.merge({:env => env, :problems => problems, :root_elements => options[:root_elements]}))
979
1338
  return env, problems
980
1339
  end
981
1340
 
@@ -987,9 +1346,15 @@ class InstantiatorTest < Test::Unit::TestCase
987
1346
  remaining = problems.dup
988
1347
  probs = []
989
1348
  expected.each do |e|
990
- p = problems.find{|p| p.message =~ e}
1349
+ if e.is_a?(Array)
1350
+ p = remaining.find{|p| p.message =~ e[0] && p.line == e[1]}
1351
+ else
1352
+ p = remaining.find{|p| p.message =~ e}
1353
+ end
991
1354
  probs << "expected problem not present: #{e}" if !p
992
- remaining.delete(p)
1355
+ # make sure to not delete duplicate problems at once
1356
+ idx = remaining.index(p)
1357
+ remaining.delete_at(idx) if idx
993
1358
  end
994
1359
  remaining.each do |p|
995
1360
  probs << "unexpected problem: #{p.message}, line: #{p.line}"
@@ -15,7 +15,7 @@ class SerializerTest < Test::Unit::TestCase
15
15
  module TestMM
16
16
  extend RGen::MetamodelBuilder::ModuleExtension
17
17
  SomeEnum = RGen::MetamodelBuilder::DataTypes::Enum.new(
18
- :name => "SomeEnum", :literals => [:A, :B, :'non-word*chars'])
18
+ :name => "SomeEnum", :literals => [:A, :B, :'non-word*chars', :'2you'])
19
19
  class TestNode < RGen::MetamodelBuilder::MMBase
20
20
  has_attr 'text', String
21
21
  has_attr 'integer', Integer
@@ -355,7 +355,8 @@ TestNode {
355
355
  testModel = [
356
356
  TestMM::TestNode.new(:enum => :A),
357
357
  TestMM::TestNode.new(:enum => :B),
358
- TestMM::TestNode.new(:enum => :'non-word*chars')
358
+ TestMM::TestNode.new(:enum => :'non-word*chars'),
359
+ TestMM::TestNode.new(:enum => :'2you')
359
360
  ]
360
361
  output = StringWriter.new
361
362
  serialize(testModel, TestMM, output)
@@ -363,6 +364,7 @@ TestNode {
363
364
  TestNode enum: A
364
365
  TestNode enum: B
365
366
  TestNode enum: "non-word*chars"
367
+ TestNode enum: "2you"
366
368
  ), output
367
369
  end
368
370
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rtext
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-25 00:00:00.000000000Z
12
+ date: 2012-10-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rgen
16
- requirement: &22687308 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,12 @@ dependencies:
21
21
  version: 0.6.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *22687308
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.6.0
25
30
  description: RText can be used to derive textual languages from an RGen metamodel
26
31
  with very little effort.
27
32
  email: martin dot thiede at gmx de
@@ -79,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
84
  version: '0'
80
85
  requirements: []
81
86
  rubyforge_project:
82
- rubygems_version: 1.7.2
87
+ rubygems_version: 1.8.23
83
88
  signing_key:
84
89
  specification_version: 3
85
90
  summary: Ruby Textual Modelling