rtext 0.3.0 → 0.4.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/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