summaryrefslogtreecommitdiff
path: root/lib/elixir/lib/io.ex
blob: a6ba705ded254e28c5672e2da094957672d6ac33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
defmodule IO do
  @moduledoc ~S"""
  Functions handling input/output (IO).

  Many functions in this module expect an IO device as an argument.
  An IO device must be a PID or an atom representing a process.
  For convenience, Elixir provides `:stdio` and `:stderr` as
  shortcuts to Erlang's `:standard_io` and `:standard_error`.

  The majority of the functions expect chardata. In case another type is given,
  functions will convert those types to string via the `String.Chars` protocol
  (as shown in typespecs). For more information on chardata, see the
  "IO data" section below.

  ## IO devices

  An IO device may be an atom or a PID. In case it is an atom,
  the atom must be the name of a registered process. In addition,
  Elixir provides two shortcuts:

    * `:stdio` - a shortcut for `:standard_io`, which maps to
      the current `Process.group_leader/0` in Erlang

    * `:stderr` - a shortcut for the named process `:standard_error`
      provided in Erlang

  IO devices maintain their position, which means subsequent calls to any
  reading or writing functions will start from the place where the device
  was last accessed. The position of files can be changed using the
  `:file.position/2` function.

  ## IO data

  IO data is a data type that can be used as a more efficient alternative to binaries
  in certain situations.

  A term of type **IO data** is a binary or a list containing bytes (integers within the `0..255` range)
  or nested IO data. The type is recursive. Let's see an example of one of
  the possible IO data representing the binary `"hello"`:

      [?h, "el", ["l", [?o]]]

  The built-in `t:iodata/0` type is defined in terms of `t:iolist/0`. An IO list is
  the same as IO data but it doesn't allow for a binary at the top level (but binaries
  are still allowed in the list itself).

  ### Use cases for IO data

  IO data exists because often you need to do many append operations
  on smaller chunks of binaries in order to create a bigger binary. However, in
  Erlang and Elixir concatenating binaries will copy the concatenated binaries
  into a new binary.

      def email(username, domain) do
        username <> "@" <> domain
      end

  In this function, creating the email address will copy the `username` and `domain`
  binaries. Now imagine you want to use the resulting email inside another binary:

      def welcome_message(name, username, domain) do
        "Welcome #{name}, your email is: #{email(username, domain)}"
      end

      IO.puts(welcome_message("Meg", "meg", "example.com"))
      #=> "Welcome Meg, your email is: meg@example.com"

  Every time you concatenate binaries or use interpolation (`#{}`) you are making
  copies of those binaries. However, in many cases you don't need the complete
  binary while you create it, but only at the end to print it out or send it
  somewhere. In such cases, you can construct the binary by creating IO data:

      def email(username, domain) do
        [username, ?@, domain]
      end

      def welcome_message(name, username, domain) do
        ["Welcome ", name, ", your email is: ", email(username, domain)]
      end

      IO.puts(welcome_message("Meg", "meg", "example.com"))
      #=> "Welcome Meg, your email is: meg@example.com"

  Building IO data is cheaper than concatenating binaries. Concatenating multiple
  pieces of IO data just means putting them together inside a list since IO data
  can be arbitrarily nested, and that's a cheap and efficient operation. Most of
  the IO-based APIs, such as `:gen_tcp` and `IO`, receive IO data and write it
  to the socket directly without converting it to binary.

  One drawback of IO data is that you can't do things like pattern match on the
  first part of a piece of IO data like you can with a binary, because you usually
  don't know the shape of the IO data. In those cases, you may need to convert it
  to a binary by calling `iodata_to_binary/1`, which is reasonably efficient
  since it's implemented natively in C. Other functionality, like computing the
  length of IO data, can be computed directly on the iodata by calling `iodata_length/1`.

  ### Chardata

  Erlang and Elixir also have the idea of `t:chardata/0`. Chardata is very
  similar to IO data: the only difference is that integers in IO data represent
  bytes while integers in chardata represent Unicode code points. Bytes
  (`t:byte/0`) are integers within the `0..255` range, while Unicode code points
  (`t:char/0`) are integers within the `0..0x10FFFF` range. The `IO` module provides
  the `chardata_to_string/1` function for chardata as the "counter-part" of the
  `iodata_to_binary/1` function for IO data.

  If you try to use `iodata_to_binary/1` on chardata, it will result in an
  argument error. For example, let's try to put a code point that is not
  representable with one byte, like `?π`, inside IO data:

      IO.iodata_to_binary(["The symbol for pi is: ", ?π])
      #=> ** (ArgumentError) argument error

  If we use chardata instead, it will work as expected:

      iex> IO.chardata_to_string(["The symbol for pi is: ", ?π])
      "The symbol for pi is: π"

  """

  @type device :: atom | pid
  @type nodata :: {:error, term} | :eof
  @type chardata :: String.t() | maybe_improper_list(char | chardata, String.t() | [])

  defguardp is_device(term) when is_atom(term) or is_pid(term)
  defguardp is_iodata(data) when is_list(data) or is_binary(data)

  @doc """
  Reads from the IO `device`.

  The `device` is iterated by the given number of characters, line by line if
  `:line` is given, or until `:eof`.

  It returns:

    * `data` - the output characters

    * `:eof` - end of file was encountered

    * `{:error, reason}` - other (rare) error condition;
      for instance, `{:error, :estale}` if reading from an
      NFS volume

  """
  @spec read(device, :eof | :line | non_neg_integer) :: chardata | nodata
  def read(device \\ :stdio, line_or_chars)

  # TODO: Deprecate me on v1.17
  def read(device, :all) do
    with :eof <- read(device, :eof) do
      with [_ | _] = opts <- :io.getopts(device),
           false <- Keyword.get(opts, :binary, true) do
        ~c""
      else
        _ -> ""
      end
    end
  end

  def read(device, :eof) do
    getn(device, ~c"", :eof)
  end

  def read(device, :line) do
    :io.get_line(map_dev(device), ~c"")
  end

  def read(device, count) when is_integer(count) and count >= 0 do
    :io.get_chars(map_dev(device), ~c"", count)
  end

  @doc """
  Reads from the IO `device`. The operation is Unicode unsafe.

  The `device` is iterated as specified by the `line_or_chars` argument:

    * if `line_or_chars` is an integer, it represents a number of bytes. The device is
      iterated by that number of bytes.

    * if `line_or_chars` is `:line`, the device is iterated line by line.

    * if `line_or_chars` is `:eof`, the device is iterated until `:eof`. `line_or_chars`
      can only be `:eof` since Elixir 1.13.0. `:eof` replaces the deprecated `:all`,
      with the difference that `:all` returns `""` on end of file, while `:eof` returns
      `:eof` itself.

  It returns:

    * `data` - the output bytes

    * `:eof` - end of file was encountered

    * `{:error, reason}` - other (rare) error condition;
      for instance, `{:error, :estale}` if reading from an
      NFS volume

  Note: do not use this function on IO devices in Unicode mode
  as it will return the wrong result.
  """
  @spec binread(device, :eof | :line | non_neg_integer) :: iodata | nodata
  def binread(device \\ :stdio, line_or_chars)

  # TODO: Deprecate me on v1.17
  def binread(device, :all) do
    with :eof <- binread(device, :eof), do: ""
  end

  def binread(device, :eof) do
    binread_eof(map_dev(device), "")
  end

  def binread(device, :line) do
    case :file.read_line(map_dev(device)) do
      {:ok, data} -> data
      other -> other
    end
  end

  def binread(device, count) when is_integer(count) and count >= 0 do
    case :file.read(map_dev(device), count) do
      {:ok, data} -> data
      other -> other
    end
  end

  @read_all_size 4096
  defp binread_eof(mapped_dev, acc) do
    case :file.read(mapped_dev, @read_all_size) do
      {:ok, data} -> binread_eof(mapped_dev, acc <> data)
      :eof -> if acc == "", do: :eof, else: acc
      other -> other
    end
  end

  @doc """
  Writes `chardata` to the given `device`.

  By default, the `device` is the standard output.

  ## Examples

      IO.write("sample")
      #=> sample

      IO.write(:stderr, "error")
      #=> error

  """
  @spec write(device, chardata | String.Chars.t()) :: :ok
  def write(device \\ :stdio, chardata) do
    :io.put_chars(map_dev(device), to_chardata(chardata))
  end

  @doc """
  Writes `iodata` to the given `device`.

  This operation is meant to be used with "raw" devices
  that are started without an encoding. The given `iodata`
  is written as is to the device, without conversion. For
  more information on IO data, see the "IO data" section in
  the module documentation.

  Use `write/2` for devices with encoding.

  Important: do **not** use this function on IO devices in
  Unicode mode as it will write the wrong data. In particular,
  the standard IO device is set to Unicode by default, so writing
  to stdio with this function will likely result in the wrong data
  being sent down the wire.
  """
  @spec binwrite(device, iodata) :: :ok | {:error, term}
  def binwrite(device \\ :stdio, iodata) when is_iodata(iodata) do
    :file.write(map_dev(device), iodata)
  end

  @doc """
  Writes `item` to the given `device`, similar to `write/2`,
  but adds a newline at the end.

  By default, the `device` is the standard output. It returns `:ok`
  if it succeeds.

  ## Examples

      IO.puts("Hello World!")
      #=> Hello World!

      IO.puts(:stderr, "error")
      #=> error

  """
  @spec puts(device, chardata | String.Chars.t()) :: :ok
  def puts(device \\ :stdio, item) when is_device(device) do
    :io.put_chars(map_dev(device), [to_chardata(item), ?\n])
  end

  @doc """
  Writes a `message` to stderr, along with the given `stacktrace_info`.

  The `stacktrace_info` must be one of:

    * a `__STACKTRACE__`, where all entries in the stacktrace will be
      included in the error message

    * a `Macro.Env` structure (since v1.14.0), where a single stacktrace
      entry from the compilation environment will be used

    * a keyword list with at least the `:file` option representing
      a single stacktrace entry (since v1.14.0). The `:line`, `:module`,
      `:function` options are also supported

  This function also notifies the compiler a warning was printed
  (in case --warnings-as-errors was enabled). It returns `:ok`
  if it succeeds.

  ## Examples

      stacktrace = [{MyApp, :main, 1, [file: 'my_app.ex', line: 4]}]
      IO.warn("variable bar is unused", stacktrace)
      #=> warning: variable bar is unused
      #=>   my_app.ex:4: MyApp.main/1

  """
  @spec warn(chardata | String.Chars.t(), Exception.stacktrace() | keyword() | Macro.Env.t()) ::
          :ok
  def warn(message, stacktrace_info)

  def warn(message, []) do
    message = [to_chardata(message), ?\n]
    :elixir_errors.print_diagnostic(:warning, 0, nil, message, message)
  end

  def warn(message, %Macro.Env{} = env) do
    warn(message, Macro.Env.stacktrace(env))
  end

  def warn(message, [{_, _} | _] = keyword) do
    if file = keyword[:file] do
      warn(
        message,
        %{
          __ENV__
          | module: keyword[:module],
            function: keyword[:function],
            line: keyword[:line],
            file: file
        }
      )
    else
      warn(message, [])
    end
  end

  def warn(message, [{_, _, _, opts} | _] = stacktrace) do
    message = to_chardata(message)
    formatted_trace = Enum.map_join(stacktrace, "\n  ", &Exception.format_stacktrace_entry(&1))
    line = opts[:line]
    file = opts[:file]

    :elixir_errors.print_diagnostic(
      :warning,
      line || 0,
      file && List.to_string(file),
      message,
      [message, ?\n, "  ", formatted_trace, ?\n]
    )
  end

  @doc false
  def warn_once(key, message, stacktrace_drop_levels) do
    {:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
    stacktrace = Enum.drop(stacktrace, stacktrace_drop_levels)

    if :elixir_config.warn(key, stacktrace) do
      warn(message, stacktrace)
    else
      :ok
    end
  end

  @doc """
  Writes a `message` to stderr, along with the current stacktrace.

  It returns `:ok` if it succeeds.

  Do not call this function at the tail of another function. Due to tail
  call optimization, a stacktrace entry would not be added and the
  stacktrace would be incorrectly trimmed. Therefore make sure at least
  one expression (or an atom such as `:ok`) follows the `IO.warn/1` call.

  ## Examples

      IO.warn("variable bar is unused")
      #=> warning: variable bar is unused
      #=>   (iex) evaluator.ex:108: IEx.Evaluator.eval/4

  """
  @spec warn(chardata | String.Chars.t()) :: :ok
  def warn(message) do
    {:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
    warn(message, Enum.drop(stacktrace, 2))
  end

  @doc """
  Inspects and writes the given `item` to the device.

  It's important to note that it returns the given `item` unchanged.
  This makes it possible to "spy" on values by inserting an
  `IO.inspect/2` call almost anywhere in your code, for example,
  in the middle of a pipeline.

  It enables pretty printing by default with width of
  80 characters. The width can be changed by explicitly
  passing the `:width` option.

  The output can be decorated with a label, by providing the `:label`
  option to easily distinguish it from other `IO.inspect/2` calls.
  The label will be printed before the inspected `item`.

  See `Inspect.Opts` for a full list of remaining formatting options.

  ## Examples

      IO.inspect(<<0, 1, 2>>, width: 40)

  Prints:

      <<0, 1, 2>>

  We can use the `:label` option to decorate the output:

      IO.inspect(1..100, label: "a wonderful range")

  Prints:

      a wonderful range: 1..100

  The `:label` option is especially useful with pipelines:

      [1, 2, 3]
      |> IO.inspect(label: "before")
      |> Enum.map(&(&1 * 2))
      |> IO.inspect(label: "after")
      |> Enum.sum()

  Prints:

      before: [1, 2, 3]
      after: [2, 4, 6]

  """
  @spec inspect(item, keyword) :: item when item: var
  def inspect(item, opts \\ []) do
    inspect(:stdio, item, opts)
  end

  @doc """
  Inspects `item` according to the given options using the IO `device`.

  See `inspect/2` for a full list of options.
  """
  @spec inspect(device, item, keyword) :: item when item: var
  def inspect(device, item, opts) when is_device(device) and is_list(opts) do
    label = if label = opts[:label], do: [to_chardata(label), ": "], else: []
    opts = Inspect.Opts.new(opts)
    doc = Inspect.Algebra.group(Inspect.Algebra.to_doc(item, opts))
    chardata = Inspect.Algebra.format(doc, opts.width)
    puts(device, [label, chardata])
    item
  end

  @doc """
  Gets a number of bytes from IO device `:stdio`.

  If `:stdio` is a Unicode device, `count` implies
  the number of Unicode code points to be retrieved.
  Otherwise, `count` is the number of raw bytes to be retrieved.

  See `IO.getn/3` for a description of return values.
  """
  @spec getn(
          device | chardata | String.Chars.t(),
          pos_integer | :eof | chardata | String.Chars.t()
        ) ::
          chardata | nodata
  def getn(prompt, count \\ 1)

  def getn(prompt, :eof) do
    getn(:stdio, prompt, :eof)
  end

  def getn(prompt, count) when is_integer(count) and count > 0 do
    getn(:stdio, prompt, count)
  end

  def getn(device, prompt) when not is_integer(prompt) do
    getn(device, prompt, 1)
  end

  @doc """
  Gets a number of bytes from the IO `device`.

  If the IO `device` is a Unicode device, `count` implies
  the number of Unicode code points to be retrieved.
  Otherwise, `count` is the number of raw bytes to be retrieved.

  It returns:

    * `data` - the input characters

    * `:eof` - end of file was encountered

    * `{:error, reason}` - other (rare) error condition;
      for instance, `{:error, :estale}` if reading from an
      NFS volume

  """
  @spec getn(device, chardata | String.Chars.t(), pos_integer | :eof) :: chardata | nodata
  def getn(device, prompt, :eof) do
    getn_eof(map_dev(device), to_chardata(prompt), [])
  end

  def getn(device, prompt, count) when is_integer(count) and count > 0 do
    :io.get_chars(map_dev(device), to_chardata(prompt), count)
  end

  defp getn_eof(device, prompt, acc) do
    case :io.get_line(device, prompt) do
      line when is_binary(line) or is_list(line) -> getn_eof(device, ~c"", [line | acc])
      :eof -> wrap_eof(:lists.reverse(acc))
      other -> other
    end
  end

  defp wrap_eof([h | _] = acc) when is_binary(h), do: IO.iodata_to_binary(acc)
  defp wrap_eof([h | _] = acc) when is_list(h), do: :lists.flatten(acc)
  defp wrap_eof([]), do: :eof

  @doc ~S"""
  Reads a line from the IO `device`.

  It returns:

    * `data` - the characters in the line terminated
      by a line-feed (LF) or end of file (EOF)

    * `:eof` - end of file was encountered

    * `{:error, reason}` - other (rare) error condition;
      for instance, `{:error, :estale}` if reading from an
      NFS volume

  ## Examples

  To display "What is your name?" as a prompt and await user input:

      IO.gets("What is your name?\n")

  """
  @spec gets(device, chardata | String.Chars.t()) :: chardata | nodata
  def gets(device \\ :stdio, prompt) do
    :io.get_line(map_dev(device), to_chardata(prompt))
  end

  @doc """
  Returns a line-based `IO.Stream` on `:stdio`.

  This is equivalent to:

      IO.stream(:stdio, :line)

  """
  @doc since: "1.12.0"
  def stream, do: stream(:stdio, :line)

  @doc """
  Converts the IO `device` into an `IO.Stream`.

  An `IO.Stream` implements both `Enumerable` and
  `Collectable`, allowing it to be used for both read
  and write.

  The `device` is iterated by the given number of characters or line by line if
  `:line` is given.

  This reads from the IO as UTF-8. Check out
  `IO.binstream/2` to handle the IO as a raw binary.

  Note that an IO stream has side effects and every time
  you go over the stream you may get different results.

  `stream/0` has been introduced in Elixir v1.12.0,
  while `stream/2` has been available since v1.0.0.

  ## Examples

  Here is an example on how we mimic an echo server
  from the command line:

      Enum.each(IO.stream(:stdio, :line), &IO.write(&1))

  Another example where you might want to collect a user input
  every new line and break on an empty line, followed by removing
  redundant new line characters (`"\n"`):

      IO.stream(:stdio, :line)
      |> Enum.take_while(&(&1 != "\n"))
      |> Enum.map(&String.replace(&1, "\n", ""))

  """
  @spec stream(device, :line | pos_integer) :: Enumerable.t()
  def stream(device \\ :stdio, line_or_codepoints)
      when line_or_codepoints == :line
      when is_integer(line_or_codepoints) and line_or_codepoints > 0 do
    IO.Stream.__build__(map_dev(device), false, line_or_codepoints)
  end

  @doc """
  Returns a raw, line-based `IO.Stream` on `:stdio`. The operation is Unicode unsafe.

  This is equivalent to:

      IO.binstream(:stdio, :line)

  """
  @doc since: "1.12.0"
  def binstream, do: binstream(:stdio, :line)

  @doc """
  Converts the IO `device` into an `IO.Stream`. The operation is Unicode unsafe.

  An `IO.Stream` implements both `Enumerable` and
  `Collectable`, allowing it to be used for both read
  and write.

  The `device` is iterated by the given number of bytes or line by line if
  `:line` is given. This reads from the IO device as a raw binary.

  Note that an IO stream has side effects and every time
  you go over the stream you may get different results.

  Finally, do not use this function on IO devices in Unicode
  mode as it will return the wrong result.

  `binstream/0` has been introduced in Elixir v1.12.0,
  while `binstream/2` has been available since v1.0.0.
  """
  @spec binstream(device, :line | pos_integer) :: Enumerable.t()
  def binstream(device \\ :stdio, line_or_bytes)
      when line_or_bytes == :line
      when is_integer(line_or_bytes) and line_or_bytes > 0 do
    IO.Stream.__build__(map_dev(device), true, line_or_bytes)
  end

  @doc """
  Converts chardata into a string.

  For more information about chardata, see the ["Chardata"](#module-chardata)
  section in the module documentation.

  In case the conversion fails, it raises an `UnicodeConversionError`.
  If a string is given, it returns the string itself.

  ## Examples

      iex> IO.chardata_to_string([0x00E6, 0x00DF])
      "æß"

      iex> IO.chardata_to_string([0x0061, "bc"])
      "abc"

      iex> IO.chardata_to_string("string")
      "string"

  """
  @spec chardata_to_string(chardata) :: String.t()
  def chardata_to_string(chardata)

  def chardata_to_string(string) when is_binary(string) do
    string
  end

  def chardata_to_string(list) when is_list(list) do
    List.to_string(list)
  end

  @doc """
  Converts IO data into a binary

  The operation is Unicode unsafe.

  Note that this function treats integers in the given IO data as
  raw bytes and does not perform any kind of encoding conversion.
  If you want to convert from a charlist to a UTF-8-encoded string,
  use `chardata_to_string/1` instead. For more information about
  IO data and chardata, see the ["IO data"](#module-io-data) section in the
  module documentation.

  If this function receives a binary, the same binary is returned.

  Inlined by the compiler.

  ## Examples

      iex> bin1 = <<1, 2, 3>>
      iex> bin2 = <<4, 5>>
      iex> bin3 = <<6>>
      iex> IO.iodata_to_binary([bin1, 1, [2, 3, bin2], 4 | bin3])
      <<1, 2, 3, 1, 2, 3, 4, 5, 4, 6>>

      iex> bin = <<1, 2, 3>>
      iex> IO.iodata_to_binary(bin)
      <<1, 2, 3>>

  """
  @spec iodata_to_binary(iodata) :: binary
  def iodata_to_binary(iodata) do
    :erlang.iolist_to_binary(iodata)
  end

  @doc """
  Returns the size of an IO data.

  For more information about IO data, see the ["IO data"](#module-io-data)
  section in the module documentation.

  Inlined by the compiler.

  ## Examples

      iex> IO.iodata_length([1, 2 | <<3, 4>>])
      4

  """
  @spec iodata_length(iodata) :: non_neg_integer
  def iodata_length(iodata) do
    :erlang.iolist_size(iodata)
  end

  @doc false
  def each_stream(device, line_or_codepoints) do
    case read(device, line_or_codepoints) do
      :eof ->
        {:halt, device}

      {:error, reason} ->
        raise IO.StreamError, reason: reason

      data ->
        {[data], device}
    end
  end

  @doc false
  def each_binstream(device, line_or_chars) do
    case binread(device, line_or_chars) do
      :eof ->
        {:halt, device}

      {:error, reason} ->
        raise IO.StreamError, reason: reason

      data ->
        {[data], device}
    end
  end

  @compile {:inline, map_dev: 1, to_chardata: 1}

  # Map the Elixir names for standard IO and error to Erlang names
  defp map_dev(:stdio), do: :standard_io
  defp map_dev(:stderr), do: :standard_error
  defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other

  defp to_chardata(list) when is_list(list), do: list
  defp to_chardata(other), do: to_string(other)
end