PageRenderTime 16ms CodeModel.GetById 2ms app.highlight 7ms RepoModel.GetById 1ms app.codeStats 1ms

/src/lib/io/low_level/input_stream_tools.e

http://github.com/tybor/Liberty
Specman e | 659 lines | 512 code | 34 blank | 113 comment | 41 complexity | 4813f3ce235c3b37e7676360dc792c61 MD5 | raw file
  1-- This file is part of a Liberty Eiffel library.
  2-- See the full copyright at the end.
  3--
  4deferred class INPUT_STREAM_TOOLS
  5
  6feature {ANY}
  7   read_character
  8         -- If `read_character' fail, `end_of_input' is set. Else, use `last_character'.
  9         -- Read tutorial or io cluster documentation to learn the `read_character' usage pattern.
 10      require
 11         is_connected
 12         not is_filtered and then can_read_character
 13      deferred
 14      ensure
 15         not end_of_input implies valid_last_character
 16      end
 17
 18   read_line_in (buffer: STRING)
 19         -- Same job as `read_line' but storage is directly done in `buffer'.
 20         -- Read tutorial or io cluster documentation to learn the `read_line' usage pattern.
 21      require
 22         is_connected
 23         not is_filtered and then can_read_line
 24         buffer /= Void
 25      deferred
 26      end
 27
 28   read_available_in (buffer: STRING; limit: INTEGER)
 29         -- Same job as `read_available' but storage is directly done in `buffer'.
 30         -- The usage pattern is the same as for `read_line'.
 31      require
 32         is_connected
 33         not is_filtered
 34         buffer /= Void
 35         limit > 0
 36      deferred
 37      end
 38
 39   unread_character
 40      require
 41         is_connected
 42         not is_filtered and then can_unread_character
 43      deferred
 44      end
 45
 46   last_character: CHARACTER
 47      require
 48         is_connected
 49         not end_of_input
 50         not is_filtered and then valid_last_character
 51      deferred
 52      end
 53
 54   can_read_character: BOOLEAN
 55         -- Note that this state is usually temporary. Usually it is called until either it becomes True or
 56         -- the stream is disconnected:
 57         --
 58         --  from
 59         --  until
 60         --     stream.can_read_character or else not stream.is_connected
 61         --  loop
 62         --     -- some "still waiting..." code, maybe a sandglass?
 63         --  end
 64         --  if stream.is_connected then
 65         --     stream.read_character
 66         --  end
 67         --
 68         -- See also: `is_connected', `end_of_input'
 69      require
 70         is_connected
 71      deferred
 72      ensure
 73         Result implies not end_of_input
 74      end
 75
 76   can_read_line: BOOLEAN
 77      require
 78         is_connected
 79      deferred
 80      end
 81
 82   can_unread_character: BOOLEAN
 83      require
 84         is_connected
 85         valid_last_character
 86      deferred
 87      end
 88
 89   valid_last_character: BOOLEAN
 90      require
 91         is_connected
 92      deferred
 93      end
 94
 95   end_of_input: BOOLEAN
 96         -- `end_of_input' means the previous attempt in character reading failed because the end has been
 97         -- reached. So `last_character' is not valid and you are not allowed to do any read attempt
 98         -- anymore.
 99         --
100         -- '''Notes:'''
101         -- * Just after a successful connect, `end_of_input' is always False because you did not yet read
102         -- anything).
103         -- * Once the end of input has been reached, this is definitive in that no more characters will ever
104         -- be provided (but of course you can still come back if `can_unread_character'; `end_of_input'
105         -- will be changed if you `unread_character'). In that, it is different from `can_read_character'
106         -- which is only a temporary state.
107         --
108         -- Please refer to the Liberty Eiffel FAQ or tutorial/io examples.
109      deferred
110      end
111
112   is_filtered: BOOLEAN
113      deferred
114      end
115
116   is_connected: BOOLEAN
117      deferred
118      end
119
120feature {ANY} -- Skipping separators:
121   skip_separators
122         -- Skip all separators (see `is_separator' of class CHARACTER) and make the first non-separator
123         -- available in `last_character'. This non-separator character is pushed back into the stream (see
124         -- `unread_character') to be available one more time (the next `read_character' will consider this
125         -- non-separator). When `end_of_input' occurs, this process is automatically stopped.
126      require
127         is_connected
128         not is_filtered
129      do
130         from
131            read_character
132         until
133            end_of_input or else not last_character.is_separator
134         loop
135            read_character
136         end
137         if not end_of_input then
138            unread_character
139         end
140      end
141
142   skip_separators_using (separators: STRING)
143         -- Same job as `skip_separators' using the `separators' set.
144      require
145         is_connected
146         not is_filtered
147         separators /= Void
148      do
149         from
150            read_character
151         until
152            end_of_input or else not separators.has(last_character)
153         loop
154            read_character
155         end
156         if not end_of_input and then can_unread_character then
157            unread_character
158         end
159      end
160
161   skip_remainder_of_line
162         -- Skip all the remainder of the line including the end of line delimiter itself.
163      require
164         is_connected
165         not is_filtered
166      local
167         stop: BOOLEAN
168      do
169         from
170         until
171            stop
172         loop
173            if end_of_input then
174               stop := True
175            else
176               inspect
177                  last_character -- See for example: http://www.mrx.net/c/introduction.html
178               when '%N' then
179                  -- UNIX format
180                  stop := True
181               when '%R' then
182                  read_character
183                  if not end_of_input then
184                     if last_character = '%N' then
185                        -- DOS format
186                     end
187                  end
188                  stop := True
189               else
190                  read_character
191               end
192            end
193         end
194      end
195
196feature {ANY} -- To read one number at a time:
197   read_integer
198         -- Read an integer according to the Eiffel syntax.  Make result available in `last_integer'.  Heading
199         -- separators are automatically skipped using `is_separator' of class CHARACTER.  Trailing separators
200         -- are not skipped.
201      require
202         is_connected
203         not is_filtered
204      local
205         state: INTEGER; sign: BOOLEAN
206      do
207         -- state = 0 : waiting sign or first digit.
208         -- state = 1 : sign read, waiting first digit.
209         -- state = 2 : in the number.
210         -- state = 3 : end state.
211         -- state = 4 : error state.
212         from
213         until
214            state > 2
215         loop
216            read_character
217            if end_of_input then
218               inspect
219                  state
220               when 0 .. 1 then
221                  state := 4
222               when 2 then
223                  state := 3
224                  -- no else, because state > 2 is impossible
225               end
226            else
227               inspect
228                  state
229               when 0 then
230                  if last_character.is_separator then
231                  elseif last_character.is_digit then
232                     last_integer := last_character.value
233                     state := 2
234                  elseif last_character = '-' then
235                     sign := True
236                     state := 1
237                  elseif last_character = '+' then
238                     state := 1
239                  else
240                     state := 4
241                  end
242               when 1 then
243                  if last_character.is_separator then
244                  elseif last_character.is_digit then
245                     last_integer := last_character.value
246                     state := 2
247                  else
248                     state := 4
249                  end
250               else
251                  if last_character.is_digit then
252                     last_integer := last_integer * 10 + last_character.value
253                  else
254                     state := 3
255                  end
256               end
257            end
258         end
259         if can_unread_character and then not end_of_input then
260            unread_character
261         end
262         valid_last_integer := state /= 4
263         if sign then
264            last_integer := -last_integer
265         end
266      ensure
267         old end_of_input implies end_of_input and then not valid_last_integer
268      end
269
270   last_integer: INTEGER
271         -- Last integer read using `read_integer'.
272
273   valid_last_integer: BOOLEAN
274         -- Was the last call to `read_integer' successful ?
275
276   last_real: REAL
277         -- Last real read with `read_real'.
278
279   valid_last_real: BOOLEAN
280         -- Was the last call to `read_real' successful ?
281
282   read_real
283         -- Read a REAL and make the result available in `last_real'.
284      require
285         is_connected
286         not is_filtered
287      local
288         state: INTEGER; sign: BOOLEAN
289      do
290         -- state = 0 : waiting sign or first digit.
291         -- state = 1 : sign read, waiting first digit.
292         -- state = 2 : in the integral part.
293         -- state = 3 : in the fractional part.
294         -- state = 4 : read 'E' or 'e' for scientific notation
295         -- state = 5 : read sign of exponent
296         -- state = 6 : in exponent
297         -- state = 7 : end state.
298         -- state = 8 : error state.
299         from
300            last_string.clear_count
301         until
302            state >= 7
303         loop
304            read_character
305            if end_of_input then
306               inspect
307                  state
308               when 2, 3, 6, 7 then
309                  state := 7
310               else
311                  state := 8
312               end
313            else
314               inspect
315                  state
316               when 0 then
317                  if last_character.is_separator then
318                  elseif last_character.is_digit then
319                     last_string.add_last(last_character)
320                     state := 2
321                  elseif last_character = '-' then
322                     sign := True
323                     state := 1
324                  elseif last_character = '+' then
325                     state := 1
326                  elseif last_character = '.' then
327                     last_string.add_last(last_character)
328                     state := 3
329                  else
330                     state := 8
331                  end
332               when 1 then
333                  if last_character.is_separator then
334                  elseif last_character.is_digit then
335                     last_string.add_last(last_character)
336                     state := 2
337                  elseif last_character = '.' then
338                     last_string.add_last(last_character)
339                     state := 3
340                  else
341                     state := 8
342                  end
343               when 2 then
344                  if last_character.is_digit then
345                     last_string.add_last(last_character)
346                  elseif last_character = '.' then
347                     last_string.add_last(last_character)
348                     state := 3
349                  elseif (once "eE").has(last_character) then
350                     last_string.add_last(last_character)
351                     state := 4
352                  else
353                     state := 7
354                  end
355               when 3 then
356                  if last_character.is_digit then
357                     last_string.add_last(last_character)
358                  elseif (once "eE").has(last_character) then
359                     last_string.add_last(last_character)
360                     state := 4
361                  else
362                     state := 7
363                  end
364               when 4 then
365                  if last_character.is_separator then
366                     state := 8
367                  elseif last_character.is_digit then
368                     last_string.add_last(last_character)
369                     state := 6
370                  elseif (once "+-").has(last_character) then
371                     last_string.add_last(last_character)
372                     state := 5
373                  else
374                     state := 8
375                  end
376               when 5 then
377                  if last_character.is_digit then
378                     last_string.add_last(last_character)
379                     state := 6
380                  else
381                     state := 8
382                  end
383               when 6 then
384                  if last_character.is_digit then
385                     last_string.add_last(last_character)
386                  else
387                     state := 7
388                  end
389               end
390            end
391         end
392         if not end_of_input and then can_unread_character then
393            unread_character
394         end
395         valid_last_real := state /= 8
396         if last_string.count > 0 then
397            -- Wouldn't "if valid_last_real then" be better ? <FM-06/07/2005>
398            last_real := last_string.to_real
399         else
400            last_real := 0 -- NaN
401         end
402         if sign then
403            last_real := -last_real
404         end
405      ensure
406         old end_of_input implies end_of_input and then not valid_last_real
407      end
408
409feature {ANY} -- To read one line or one word at a time:
410   last_string: STRING
411         -- Access to the unique common buffer to get for example the result computed by `read_line',
412         -- `read_word', `newline', etc. This is a once function (the same common buffer is used for all
413         -- streams).
414      once
415         create Result.make(1024)
416      end
417
418   read_line
419         -- Read a complete line ended by '%N' or `end_of_input'. Make the result available in `last_string'
420         -- common buffer. The end of line character (usually '%N') is not added in the `last_string' buffer.
421         -- Read tutorial or io cluster documentation to learn the read_line usage pattern.
422         --
423         -- See also `read_line_in'.
424      require
425         is_connected
426         not is_filtered
427      do
428         last_string.clear_count
429         read_line_in(last_string)
430      end
431
432   read_available (limit: INTEGER)
433         -- Read as many characters as possible, as long as the stream does not block and up to the given limit.
434         --
435         -- See also `read_available_in'.
436      require
437         is_connected
438         not is_filtered
439      do
440         last_string.clear_count
441         read_available_in(last_string, limit)
442      end
443
444   read_word
445         -- Read a word using `is_separator' of class CHARACTER. The result is available in the `last_string'
446         -- common buffer. Heading separators are automatically skipped. Trailing separators are not skipped
447         -- (`last_character' is left on the first one). If `end_of_input' is encountered, Result can be the
448         -- empty string.
449      require
450         is_connected
451         not is_filtered
452      do
453         skip_separators
454         if not end_of_input then
455            read_character
456         end
457         from
458            last_string.clear_count
459         until
460            end_of_input or else last_character.is_separator
461         loop
462            last_string.extend(last_character)
463            read_character
464         end
465      end
466
467   newline
468         -- Consume input until newline ('%N') is found. The corresponding STRING is stored in `last_string'
469         -- common buffer.
470      require
471         is_connected
472         not is_filtered
473      local
474         stop: BOOLEAN
475      do
476         from
477            last_string.clear_count
478            stop := end_of_input
479         until
480            stop
481         loop
482            read_character
483            if end_of_input or else last_character = '%N' then
484               stop := True
485            else
486               last_string.extend(last_character)
487            end
488         end
489      end
490
491   reach_and_skip (keyword: STRING)
492         -- Try to skip enough characters in order to reach the `keyword' which is skipped too. If the
493         -- `keyword' is not in the remainder of this stream, the process is stopped as soon as `end_of_input'
494         -- occurs.
495      require
496         is_connected
497         not is_filtered
498         not keyword.is_empty
499      local
500         stop: BOOLEAN; i: INTEGER; first: CHARACTER
501      do
502         from
503            last_string.clear_count
504            first := keyword.first
505         until
506            end_of_input or else stop
507         loop
508            -- Reach the first character of the `keyword':
509            from
510               i := 2
511            until
512               i > last_string.count or else last_string.item(i) = first
513            loop
514               i := i + 1
515            end
516            if i <= last_string.count then
517               last_string.remove_head(i - 1)
518            else
519               last_string.clear_count
520               from
521                  if not end_of_input then
522                     read_character
523                  end
524               until
525                  end_of_input or else last_character = first
526               loop
527                  read_character
528               end
529               last_string.extend(first)
530            end
531            check
532               not end_of_input implies last_string.item(1) = first
533               last_string.count <= keyword.count
534            end
535            -- Now we need as many characters as in `keyword':
536            if not (end_of_input or else last_string.count = keyword.count) then
537               from
538                  read_character
539               until
540                  end_of_input or else last_string.count = keyword.count - 1
541               loop
542                  last_string.extend(last_character)
543                  read_character
544               end
545               if not end_of_input then
546                  last_string.extend(last_character)
547               end
548            end
549            stop := last_string.is_equal(keyword)
550         end
551         if not end_of_input then
552            read_character
553            unread_character
554         end
555      ensure
556         not end_of_input implies last_string.is_equal(keyword)
557      end
558
559feature {ANY} -- Other features:
560   read_word_using (separators: STRING)
561         -- Same job as `read_word' using `separators'.
562      require
563         is_connected
564         not is_filtered
565         separators /= Void
566      do
567         skip_separators_using(separators)
568         if not end_of_input then
569            read_character
570         end
571         from
572            last_string.clear_count
573         until
574            end_of_input or else separators.has(last_character)
575         loop
576            last_string.extend(last_character)
577            read_character
578         end
579      end
580
581   read_tail_in (str: STRING)
582         -- Read all remaining character of the stream in `str'.
583      require
584         is_connected
585         not is_filtered
586         str /= Void
587      do
588         from
589         until
590            end_of_input
591         loop
592            read_character
593            if not end_of_input then
594               str.extend(last_character)
595            end
596         end
597      ensure
598         end_of_input
599      end
600
601feature {}
602   io_getc (stream: POINTER): INTEGER
603      external "plug_in"
604      alias "{
605    location: "${sys}/plugins"
606    module_name: "io"
607    feature_name: "io_getc"
608    }"
609      end
610
611   io_ungetc (byte: CHARACTER; stream: POINTER)
612      external "plug_in"
613      alias "{
614    location: "${sys}/plugins"
615    module_name: "io"
616    feature_name: "io_ungetc"
617    }"
618      end
619
620   io_fread (buf: NATIVE_ARRAY[CHARACTER]; size: INTEGER; stream: POINTER): INTEGER
621         -- return size read or 0 if end of input (-1 on error => exception ?)
622      external "plug_in"
623      alias "{
624    location: "${sys}/plugins"
625    module_name: "io"
626    feature_name: "io_fread"
627    }"
628      end
629
630   eof: INTEGER
631      external "plug_in"
632      alias "{
633    location: "${sys}/plugins"
634    module_name: "io"
635    feature_name: "EOF"
636    }"
637      end
638
639end -- class INPUT_STREAM_TOOLS
640--
641-- Copyright (C) 2009-2017: by all the people cited in the AUTHORS file.
642--
643-- Permission is hereby granted, free of charge, to any person obtaining a copy
644-- of this software and associated documentation files (the "Software"), to deal
645-- in the Software without restriction, including without limitation the rights
646-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
647-- copies of the Software, and to permit persons to whom the Software is
648-- furnished to do so, subject to the following conditions:
649--
650-- The above copyright notice and this permission notice shall be included in
651-- all copies or substantial portions of the Software.
652--
653-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
654-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
655-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
656-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
657-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
658-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
659-- THE SOFTWARE.