PageRenderTime 48ms CodeModel.GetById 35ms app.highlight 8ms RepoModel.GetById 1ms app.codeStats 0ms

/src/tools/configuration/etc/liberty_etc_visitor_impl.e

http://github.com/tybor/Liberty
Specman e | 843 lines | 747 code | 81 blank | 15 comment | 31 complexity | 52dd43f1620b1032ad5f664060344ddb MD5 | raw file
  1-- This file is part of Liberty Eiffel.
  2--
  3-- Liberty Eiffel is free software: you can redistribute it and/or modify
  4-- it under the terms of the GNU General Public License as published by
  5-- the Free Software Foundation, version 3 of the License.
  6--
  7-- Liberty Eiffel is distributed in the hope that it will be useful,
  8-- but WITHOUT ANY WARRANTY; without even the implied warranty of
  9-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 10-- GNU General Public License for more details.
 11--
 12-- You should have received a copy of the GNU General Public License
 13-- along with Liberty Eiffel.  If not, see <http://www.gnu.org/licenses/>.
 14--
 15class LIBERTY_ETC_VISITOR_IMPL
 16
 17inherit
 18   LIBERTY_ETC_VISITOR
 19
 20create {ANY}
 21   make
 22
 23feature {LIBERTY_ETC}
 24   tool_name: FIXED_STRING
 25
 26   clusters: MAP[LIBERTY_ETC_CLUSTER, FIXED_STRING] is
 27      do
 28         Result := all_clusters
 29      end
 30
 31   check_validity is
 32      local
 33         fix_point: BOOLEAN; mark: INTEGER
 34      do
 35         from
 36            all_clusters.do_all(agent {LIBERTY_ETC_CLUSTER}.check_validity(clusters))
 37            all_clusters.do_all(agent {LIBERTY_ETC_CLUSTER}.check_cycles)
 38            check not fix_point end
 39         until
 40            fix_point
 41         loop
 42            mark := mark + 1
 43            if mark > 1024 then
 44               std_error.put_line("No fix point after 1024 iterations, clusters graph too complex?")
 45               die_with_code(1)
 46            end
 47            fix_point := not all_clusters.exists(agent {LIBERTY_ETC_CLUSTER}.fix_depth(mark))
 48         end
 49      end
 50
 51feature {LIBERTY_ETC_FACTORY} -- Lists
 52   visit_environment_variable_list (list: LIBERTY_ETC_LIST) is
 53      do
 54         list.do_all(agent {EIFFEL_NODE}.accept(Current))
 55      end
 56
 57   visit_cluster_list (list: LIBERTY_ETC_LIST) is
 58      do
 59         list.do_all(agent {EIFFEL_NODE}.accept(Current))
 60      end
 61
 62   visit_cluster_configuration_list (list: LIBERTY_ETC_LIST) is
 63      do
 64         list.do_all(agent {EIFFEL_NODE}.accept(Current))
 65      end
 66
 67   visit_debug_configuration_list (list: LIBERTY_ETC_LIST) is
 68      do
 69         list.do_all(agent {EIFFEL_NODE}.accept(Current))
 70      end
 71
 72   visit_debug_key_list (list: LIBERTY_ETC_LIST) is
 73      do
 74         list.do_all(agent {EIFFEL_NODE}.accept(Current))
 75      end
 76
 77   visit_location_list (list: LIBERTY_ETC_LIST) is
 78      do
 79         create last_locations.with_capacity(2)
 80         list.do_all(agent {EIFFEL_NODE}.accept(Current))
 81      end
 82
 83feature {LIBERTY_ETC_FACTORY} -- Non-Terminals
 84   visit_master (nt: LIBERTY_ETC_NON_TERMINAL) is
 85      local
 86         t: EIFFEL_TERMINAL_NODE
 87      do
 88         check
 89            nt.lower = 0
 90            nt.name_at(1).is_equal(once "KW entity name")
 91            nt.name_at(2).is_equal(once "Environment")
 92            nt.name_at(3).is_equal(once "Clusters")
 93         end
 94         t ::= nt.node_at(1)
 95         if not t.image.image.is_equal(tool_name) then
 96            errors.set(errors.level_fatal_error, "Invalid file content: bad Master name " + t.image.image
 97                       + " (expected " + tool_name + ")")
 98            check
 99               dead: False
100            end
101         end
102         nt.node_at(2).accept(Current)
103         nt.node_at(3).accept(Current)
104      end
105
106   visit_cluster_definition (nt: LIBERTY_ETC_NON_TERMINAL) is
107      local
108         cluster_name: EIFFEL_TERMINAL_NODE
109         cluster_definition_name: FIXED_STRING
110      do
111         check
112            nt.lower = 0
113            nt.name_at(1).is_equal(once "KW cluster name")
114            nt.name_at(2).is_equal(once "Version")
115            nt.name_at(3).is_equal(once "Locations")
116            nt.name_at(4).is_equal(once "Needs")
117            nt.name_at(5).is_equal(once "Concurrency")
118            nt.name_at(6).is_equal(once "Assertion")
119            nt.name_at(7).is_equal(once "Debug")
120            nt.name_at(8).is_equal(once "Environment")
121            nt.name_at(9).is_equal(once "Clusters")
122         end
123
124         check
125            current_cluster = Void
126         end
127         cluster_name ::= nt.node_at(1)
128         cluster_definition_name := cluster_name.image.image.intern
129         if current_cluster_name = Void then
130            current_cluster_name := cluster_definition_name
131         elseif current_cluster_name /= cluster_definition_name then
132            errors.set(errors.level_fatal_error, "Invalid file content: bad cluster name " + cluster_definition_name
133                       + " (expected " + current_cluster_name + ")")
134            check
135               dead: False
136            end
137         end
138         if all_clusters.fast_has(cluster_definition_name) then
139            errors.set(errors.level_fatal_error, "Invalid file content: duplicate cluster name " + cluster_definition_name)
140            check
141               dead: False
142            end
143         end
144
145         nt.node_at(8).accept(Current)
146         nt.node_at(9).accept(Current)
147         nt.node_at(3).accept(Current)
148
149         set_current_cluster_from_last_locations
150
151         nt.node_at(2).accept(Current)
152         nt.node_at(4).accept(Current)
153         nt.node_at(5).accept(Current)
154         nt.node_at(6).accept(Current)
155         nt.node_at(7).accept(Current)
156
157         check
158            current_cluster /= Void
159         end
160      end
161
162   visit_environment (nt: LIBERTY_ETC_NON_TERMINAL) is
163      do
164         if not nt.is_empty then
165            nt.node_at(1).accept(Current)
166         end
167      end
168
169   visit_environment_variable (nt: LIBERTY_ETC_NON_TERMINAL) is
170      local
171         entity_name, entity_value: EIFFEL_TERMINAL_NODE
172         entity_value_image: TYPED_EIFFEL_IMAGE[STRING]
173      do
174         check
175            nt.lower = 0
176            nt.name_at(0).is_equal(once "KW entity name")
177            nt.name_at(2).is_equal(once "KW string")
178         end
179         entity_name ::= nt.node_at(0)
180         entity_value ::= nt.node_at(2)
181         entity_value_image ::= entity_value.image
182         env.set(entity_name.image.image, entity_value_image.decoded)
183      end
184
185   visit_clusters (nt: LIBERTY_ETC_NON_TERMINAL) is
186      do
187         if not nt.is_empty then
188            check
189               nt.lower = 0
190               nt.name_at(1).is_equal(once "Cluster*")
191            end
192            nt.node_at(1).accept(Current)
193         end
194      end
195
196   visit_cluster (nt: LIBERTY_ETC_NON_TERMINAL) is
197      local
198         cluster_name: EIFFEL_TERMINAL_NODE
199         previous_cluster: like current_cluster
200         previous_cluster_name: like current_cluster_name
201         location: EIFFEL_TERMINAL_NODE
202         location_image: TYPED_EIFFEL_IMAGE[STRING]
203         descriptor: STRING
204      do
205         check
206            nt.lower = 0
207            nt.name_at(0).is_equal(once "KW cluster name")
208            nt.name_at(2).is_equal(once "KW string")
209            nt.name_at(3).is_equal(once "Configure")
210         end
211         cluster_name ::= nt.node_at(0)
212         previous_cluster_name := current_cluster_name
213         previous_cluster := current_cluster
214         current_cluster_name := cluster_name.image.image.intern
215         current_cluster := Void
216
217         location ::= nt.node_at(2)
218         location_image ::= location.image
219         descriptor := location_image.decoded
220         env.substitute(descriptor)
221         set_current_cluster_from_location(canonical_location(descriptor))
222         if current_cluster.name /= current_cluster_name then
223            -- an aliased cluster, usually for the PROGRAM_LOADPATH / PROGRAM_LOADPATH_ pair
224
225            if all_clusters.fast_has(current_cluster_name) then
226               errors.set(errors.level_fatal_error, "Invalid file content: duplicate cluster name " + current_cluster_name)
227               check
228                  dead: False
229               end
230            end
231
232            all_clusters.add(current_cluster, current_cluster_name)
233         end
234         nt.node_at(3).accept(Current)
235         current_cluster := previous_cluster
236         current_cluster_name := previous_cluster_name
237      end
238
239   visit_configure (nt: LIBERTY_ETC_NON_TERMINAL) is
240      do
241      end
242
243   visit_locations (nt: LIBERTY_ETC_NON_TERMINAL) is
244      do
245         check
246            nt.lower = 0
247            nt.name_at(1).is_equal(once "Location+")
248         end
249         nt.node_at(1).accept(Current)
250      end
251
252   visit_location (nt: LIBERTY_ETC_NON_TERMINAL) is
253      local
254         location: EIFFEL_TERMINAL_NODE
255         location_image: TYPED_EIFFEL_IMAGE[STRING]
256         descriptor: STRING
257      do
258         check
259            nt.lower = 0
260            nt.name_at(0).is_equal(once "KW string")
261         end
262         location ::= nt.node_at(0)
263         location_image ::= location.image
264         descriptor := once ""
265         descriptor.copy(location_image.decoded)
266         env.substitute(descriptor)
267         last_locations.add_last(canonical_location(descriptor))
268      end
269
270   set_current_cluster_from_last_locations is
271      require
272         current_cluster = Void
273         not last_locations.is_empty
274      local
275         i: INTEGER; must_scan_loadpath: BOOLEAN
276      do
277         from
278            i := last_locations.lower
279         until
280            i > last_locations.upper
281         loop
282            if not files.is_directory(last_locations.item(i)) then
283               dir.compute_short_name_of(last_locations.item(i))
284               inspect
285                  dir.last_entry
286               when "loadpath.se" then
287                  must_scan_loadpath := True
288               else
289                  std_error.put_line(last_locations.item(i) + " is not a directory")
290                  die_with_code(1)
291               end
292            end
293            i := i + 1
294         end
295         if must_scan_loadpath then
296            scan_loadpath(last_locations)
297         end
298
299         create current_cluster.make(current_cluster_name, last_locations)
300         all_clusters.add(current_cluster, current_cluster_name)
301         from
302            i := last_locations.lower
303         until
304            i > last_locations.upper
305         loop
306            cluster_per_location.add(current_cluster, last_locations.item(i))
307            i := i + 1
308         end
309         last_locations := Void
310      ensure
311         current_cluster /= Void
312         last_locations = Void
313      end
314
315   set_current_cluster_from_location (location: ABSTRACT_STRING) is
316      require
317         current_cluster = Void
318         location /= Void
319         dir.system_notation.is_absolute_path(location.out)
320      local
321         descriptor: STRING
322      do
323         if files.is_directory(location) then
324            dir.compute_file_path_with(location, once "cluster.rc")
325            if not dir.last_entry.is_empty and then files.file_exists(dir.last_entry) and then files.is_file(dir.last_entry) then
326               set_current_cluster_from_cluster_rc(dir.last_entry.intern)
327            else
328               dir.compute_file_path_with(location, once "loadpath.se")
329               if not dir.last_entry.is_empty and then files.file_exists(dir.last_entry) and then files.is_file(dir.last_entry) then
330                  set_current_cluster_from_loadpath_se(dir.last_entry.intern)
331               else
332                  descriptor := once ""
333                  descriptor.make_from_string(location)
334                  dir.system_notation.to_directory_path(descriptor)
335                  set_current_cluster_from_directory(descriptor.intern)
336               end
337            end
338         elseif files.is_file(location) then
339            dir.compute_short_name_of(location)
340            inspect
341               dir.last_entry
342            when "cluster.rc" then
343               set_current_cluster_from_cluster_rc(location.intern)
344            when "loadpath.se" then
345               set_current_cluster_from_loadpath_se(location.intern)
346            else
347               std_error.put_line("Unknown file format: " + location)
348               die_with_code(1)
349            end
350         else
351            std_error.put_line("Strange file: " + location
352                               + " is neither a directory nor a regular file - cannot create the cluster")
353            breakpoint
354            die_with_code(1)
355         end
356      ensure
357         current_cluster /= Void
358      end
359
360   visit_version (nt: LIBERTY_ETC_NON_TERMINAL) is
361      local
362         version: EIFFEL_TERMINAL_NODE
363         version_image: TYPED_EIFFEL_IMAGE[STRING]
364      do
365         check
366            nt.lower = 0
367            nt.name_at(1).is_equal(once "KW string")
368         end
369         version ::= nt.node_at(1)
370         version_image ::= version.image
371         current_cluster.set_version(version_image.decoded.intern)
372      end
373
374   visit_needs (nt: LIBERTY_ETC_NON_TERMINAL) is
375      do
376         if not nt.is_empty then
377            check
378               nt.lower = 0
379               nt.name_at(1).is_equal(once "Cluster_Configuration*")
380            end
381            nt.node_at(1).accept(Current)
382         end
383      end
384
385   visit_cluster_configuration (nt: LIBERTY_ETC_NON_TERMINAL) is
386      local
387         needed_cluster: EIFFEL_TERMINAL_NODE
388         needed_cluster_name: FIXED_STRING
389      do
390         check
391            nt.lower = 0
392            nt.name_at(0).is_equal(once "KW cluster name")
393            nt.name_at(1).is_equal(once "Cluster_Constraints")
394         end
395         needed_cluster ::= nt.node_at(0)
396         create {FAST_ARRAY[LIBERTY_ETC_CONSTRAINT]} last_cluster_constraints.with_capacity(1)
397         nt.node_at(1).accept(Current)
398         needed_cluster_name := needed_cluster.image.image.intern
399         current_cluster.add_needs(create {LIBERTY_ETC_NEEDS}.make(needed_cluster_name, all_clusters.fast_reference_at(needed_cluster_name), last_cluster_constraints))
400         last_cluster_constraints := Void
401      end
402
403   visit_cluster_constraints (nt: LIBERTY_ETC_NON_TERMINAL) is
404      do
405         if not nt.is_empty then
406            check
407               nt.lower = 0
408               nt.name_at(1).is_equal(once "Cluster_Version_Constraint")
409            end
410            nt.node_at(1).accept(Current)
411         end
412      end
413
414   visit_cluster_version_constraint (nt: LIBERTY_ETC_NON_TERMINAL) is
415      local
416         version: EIFFEL_TERMINAL_NODE
417         version_image: TYPED_EIFFEL_IMAGE[STRING]
418      do
419         check
420            nt.name_at(1).is_equal(once "Version_Operator")
421            nt.name_at(2).is_equal(once "KW string")
422         end
423         nt.node_at(1).accept(Current)
424         version ::= nt.node_at(2)
425         version_image ::= version.image
426         last_cluster_constraints.add_last(create {LIBERTY_ETC_VERSION_CONSTRAINT}.make(last_version_operator, version_image.decoded.intern))
427      end
428
429   visit_version_operator (nt: LIBERTY_ETC_NON_TERMINAL) is
430      do
431         check
432            nt.lower = 0
433         end
434         inspect
435            nt.name_at(0)
436         when "KW =" then
437            last_version_operator := agent_version_eq
438         when "KW <=" then
439            last_version_operator := agent_version_le
440         when "KW >=" then
441            last_version_operator := agent_version_ge
442         when "KW /=" then
443            last_version_operator := agent_version_ne
444         when "KW <" then
445            last_version_operator := agent_version_lt
446         when "KW >" then
447            last_version_operator := agent_version_gt
448         end
449      end
450
451   visit_assertion (nt: LIBERTY_ETC_NON_TERMINAL) is
452      do
453      end
454
455   visit_assertion_level (nt: LIBERTY_ETC_NON_TERMINAL) is
456      do
457      end
458
459   visit_debug (nt: LIBERTY_ETC_NON_TERMINAL) is
460      do
461      end
462
463   visit_debug_configuration (nt: LIBERTY_ETC_NON_TERMINAL) is
464      do
465      end
466
467   visit_debug_key (nt: LIBERTY_ETC_NON_TERMINAL) is
468      do
469      end
470
471   visit_concurrency (nt: LIBERTY_ETC_NON_TERMINAL) is
472      do
473      end
474
475feature {}
476   cluster_per_location: DICTIONARY[LIBERTY_ETC_CLUSTER, FIXED_STRING]
477
478   set_current_cluster_from_cluster_rc (cluster_rc: FIXED_STRING) is
479      require
480         dir.system_notation.is_absolute_path(cluster_rc.out)
481         current_cluster = Void
482      local
483         etc: LIBERTY_ETC
484         previous_directory: like current_directory
485      do
486         current_cluster := cluster_per_location.fast_reference_at(cluster_rc)
487         if current_cluster = Void then
488            check
489               etc.visitor = Current
490            end
491            previous_directory := current_directory
492            dir.compute_parent_directory_of(cluster_rc)
493            current_directory := dir.last_entry.intern
494
495            etc.configure_cluster_rc(cluster_rc)
496            cluster_per_location.add(current_cluster, cluster_rc)
497
498            current_directory := previous_directory
499         end
500      ensure
501         current_cluster /= Void
502         current_cluster = cluster_per_location.fast_reference_at(cluster_rc)
503      end
504
505   set_current_cluster_from_loadpath_se (loadpath_se: FIXED_STRING) is
506      require
507         dir.system_notation.is_absolute_path(loadpath_se.out)
508         current_cluster = Void
509      local
510         locations: FAST_ARRAY[FIXED_STRING]
511         i, n: INTEGER
512      do
513         std_error.put_line(once "loadpath.se support is limited and will be removed.")
514         std_error.put_line(loadpath_se)
515         std_error.put_line(once "Consider using a cluster.rc file instead.")
516
517         current_cluster := cluster_per_location.fast_reference_at(loadpath_se)
518         if current_cluster = Void then
519            if all_clusters.fast_has(current_cluster_name) then
520               errors.set(errors.level_fatal_error, "Invalid file content: duplicate cluster name " + current_cluster_name)
521               check
522                  dead: False
523               end
524            end
525
526            locations := {FAST_ARRAY[FIXED_STRING] << loadpath_se >> }
527            from
528            until
529               n = locations.count
530            loop
531               i := i + 1
532               if i > 500 then
533                  std_error.put_line("loadpath nesting too deep starting from " + loadpath_se)
534                  die_with_code(1)
535               end
536               n := locations.count
537               scan_loadpath(locations)
538            end
539            create current_cluster.make(current_cluster_name, locations)
540            all_clusters.add(current_cluster, current_cluster_name)
541            cluster_per_location.add(current_cluster, loadpath_se)
542         end
543      ensure
544         current_cluster /= Void
545         current_cluster = cluster_per_location.fast_reference_at(loadpath_se)
546      end
547
548   scan_loadpath (locations: FAST_ARRAY[FIXED_STRING]) is
549      require
550         not locations.is_empty
551      local
552         i: INTEGER; location: STRING; file: FIXED_STRING
553      do
554         location := once ""
555         from
556            i := locations.lower
557         until
558            i > locations.upper
559         loop
560            location.make_from_string(locations.item(i))
561            env.substitute(location)
562            file := canonical_location(location)
563            if files.is_directory(file) then
564               i := i + 1
565            elseif files.is_file(file) then
566               locations.remove(i)
567               import_loadpath(locations, file)
568            else
569               std_error.put_line("Strange file: " + locations.item(i)
570                                  + " is neither a directory nor a regular file - ignored")
571               breakpoint
572               locations.remove(i)
573            end
574         end
575      end
576
577   import_loadpath (locations: FAST_ARRAY[FIXED_STRING]; loadpath: FIXED_STRING) is
578      require
579         dir.system_notation.is_absolute_path(loadpath.out)
580      local
581         directory: FIXED_STRING
582      do
583         tfr.connect_to(loadpath)
584         if not tfr.is_connected then
585            std_error.put_line("Could not open " + loadpath)
586            die_with_code(1)
587         end
588
589         dir.compute_parent_directory_of(loadpath)
590         directory := dir.last_entry.intern
591
592         from
593            tfr.read_line
594         until
595            tfr.end_of_input
596         loop
597            import_loadpath_line(locations, directory, tfr.last_string)
598            tfr.read_line
599         end
600         import_loadpath_line(locations, directory, tfr.last_string)
601         tfr.disconnect
602      end
603
604   import_loadpath_line (locations: FAST_ARRAY[FIXED_STRING]; directory: FIXED_STRING; loadpath_line: STRING) is
605      require
606         dir.system_notation.is_absolute_path(directory.out)
607      local
608         loadpath_entry: STRING
609      do
610         if not is_comment(loadpath_line) then
611            loadpath_entry := once ""
612            loadpath_entry.make_from_string(loadpath_line)
613            env.substitute(loadpath_entry)
614            if dir.system_notation.is_absolute_path(loadpath_entry) then
615               locations.add_last(loadpath_entry.intern)
616            else
617               dir.compute_file_path_with(directory, loadpath_entry)
618               if files.file_exists(dir.last_entry) then
619                  locations.add_last(dir.last_entry.intern)
620               else
621                  dir.compute_subdirectory_with(directory, loadpath_entry)
622                  locations.add_last(dir.last_entry.intern)
623               end
624            end
625         end
626      end
627
628   is_comment (loadpath_line: STRING): BOOLEAN is
629      local
630         i: INTEGER; found: BOOLEAN
631      do
632         from
633            Result := True
634            i := loadpath_line.lower
635         until
636            found or else i > loadpath_line.upper
637         loop
638            inspect
639               loadpath_line.item(i)
640            when ' ', '%T' then
641               check Result end
642            when '-' then
643               Result := i < loadpath_line.upper and then loadpath_line.item(i+1) = '-'
644               found := True
645            else
646               Result := False
647               found := True
648            end
649            i := i + 1
650         end
651      end
652
653   set_current_cluster_from_directory (directory: FIXED_STRING) is
654      require
655         dir.system_notation.is_absolute_path(directory.out)
656         current_cluster = Void
657      do
658         current_cluster := cluster_per_location.fast_reference_at(directory)
659         if current_cluster = Void then
660            create current_cluster.make(current_cluster_name, {FAST_ARRAY[FIXED_STRING] << directory >> })
661            all_clusters.add(current_cluster, current_cluster_name)
662            cluster_per_location.add(current_cluster, directory)
663         end
664      ensure
665         current_cluster /= Void
666         current_cluster = cluster_per_location.fast_reference_at(directory)
667      end
668
669   canonical_location (descriptor: STRING): FIXED_STRING is
670      local
671         buffer: STRING
672      do
673         buffer := once ""
674         buffer.make_from_string(current_directory)
675         dir.system_notation.to_absolute_path_in(buffer, descriptor)
676         Result := buffer.intern
677      end
678
679feature {}
680   agent_version_eq: PREDICATE[TUPLE[FIXED_STRING, FIXED_STRING]] is
681      once
682         Result := agent version_eq
683      end
684
685   agent_version_le: PREDICATE[TUPLE[FIXED_STRING, FIXED_STRING]] is
686      once
687         Result := agent version_le
688      end
689
690   agent_version_ge: PREDICATE[TUPLE[FIXED_STRING, FIXED_STRING]] is
691      once
692         Result := agent version_ge
693      end
694
695   agent_version_ne: PREDICATE[TUPLE[FIXED_STRING, FIXED_STRING]] is
696      once
697         Result := agent version_ne
698      end
699
700   agent_version_lt: PREDICATE[TUPLE[FIXED_STRING, FIXED_STRING]] is
701      once
702         Result := agent version_lt
703      end
704
705   agent_version_gt: PREDICATE[TUPLE[FIXED_STRING, FIXED_STRING]] is
706      once
707         Result := agent version_gt
708      end
709
710   version_eq (v1, v2: FIXED_STRING): BOOLEAN is
711      do
712         Result := v1.is_equal(v2)
713      end
714
715   version_le (v1, v2: FIXED_STRING): BOOLEAN is
716      do
717         Result := not version_lt(v2, v1)
718      end
719
720   version_ge (v1, v2: FIXED_STRING): BOOLEAN is
721      do
722         Result := not version_lt(v1, v2)
723      end
724
725   version_ne (v1, v2: FIXED_STRING): BOOLEAN is
726      do
727         Result := not v1.is_equal(v2)
728      end
729
730   version_lt (v1, v2: FIXED_STRING): BOOLEAN is
731      local
732         previous_dot1, dot1, previous_dot2, dot2: INTEGER
733         finished: BOOLEAN
734         version1, version2: INTEGER
735      do
736         from
737         until
738            finished
739         loop
740            dot1 := v1.index_of('.', previous_dot1 + 1)
741            dot2 := v2.index_of('.', previous_dot2 + 1)
742
743            if not v1.valid_index(dot1) then
744               dot1 := v1.upper + 1
745               finished := True
746            end
747            if not v2.valid_index(dot2) then
748               dot2 := v2.upper + 1
749               finished := True
750            end
751
752            version1 := extract_integer(v1, previous_dot1 + 1, dot1 - 1)
753            version2 := extract_integer(v2, previous_dot2 + 1, dot2 - 1)
754            if version1 < version2 then
755               Result := True
756               finished := True
757            elseif version1 > version2 then
758               check not Result end
759               dot1 := 0
760               dot2 := 0
761               check
762                  not v1.valid_index(dot1)
763                  not v2.valid_index(dot2)
764               end
765               finished := True
766            else
767               previous_dot1 := dot1
768               previous_dot2 := dot2
769            end
770         end
771
772         if not Result then
773            check finished end
774            if v1.valid_index(dot1) then
775               check not Result end
776            elseif v2.valid_index(dot2) then
777               Result := True
778            else
779               check not Result end
780            end
781         end
782      end
783
784   version_gt (v1, v2: FIXED_STRING): BOOLEAN is
785      do
786         Result := version_lt(v2, v1)
787      end
788
789   extract_integer (v: FIXED_STRING; low, up: INTEGER): INTEGER is
790      require
791         v.valid_index(low)
792         v.valid_index(up)
793         low <= up
794      local
795         i: INTEGER
796      do
797         from
798            i := low
799         until
800            i > up
801         loop
802            Result := Result*10 + (v.item(i).code - '0'.code)
803            i := i + 1
804         end
805      end
806
807feature {}
808   make (a_tool_name: ABSTRACT_STRING) is
809      require
810         a_tool_name /= Void
811      do
812         tool_name := a_tool_name.intern
813         create all_clusters.make
814         create {HASHED_DICTIONARY[LIBERTY_ETC_CLUSTER, FIXED_STRING]} cluster_per_location.make
815         current_directory := dir.current_working_directory
816      ensure
817         tool_name = a_tool_name.intern
818      end
819
820   errors: LIBERTY_ERRORS
821   env: LIBERTY_ENVIRONMENT
822   all_clusters: HASHED_DICTIONARY[LIBERTY_ETC_CLUSTER, FIXED_STRING]
823   current_directory: FIXED_STRING
824   current_cluster_name: FIXED_STRING
825   current_cluster: LIBERTY_ETC_CLUSTER
826
827   last_locations: FAST_ARRAY[FIXED_STRING]
828   last_cluster_constraints: COLLECTION[LIBERTY_ETC_CONSTRAINT]
829   last_version_operator: PREDICATE[TUPLE[FIXED_STRING, FIXED_STRING]]
830
831   dir: BASIC_DIRECTORY
832   files: FILE_TOOLS
833
834   tfr: TEXT_FILE_READ is
835      once
836         create Result.make
837      end
838
839invariant
840   all_clusters /= Void
841   tool_name /= Void
842
843end -- class LIBERTY_ETC_VISITOR_IMPL