27
27
% %-----------------------------------------------------------------------------
28
28
-module (io_lib ).
29
29
30
- -export ([format /2 , latin1_char_list /1 ]).
30
+ -export ([
31
+ write /1 ,
32
+ format /2 ,
33
+ fwrite /2 ,
34
+ latin1_char_list /1 ,
35
+ write_atom /1 ,
36
+ printable_list /1 ,
37
+ write_string /1 ,
38
+ write_string /2 ,
39
+ chars_length /1
40
+ ]).
41
+
42
+ -export_type ([
43
+ chars / 0
44
+ ]).
45
+
46
+ -type chars () :: [char () | chars ()].
47
+
48
+ % %-----------------------------------------------------------------------------
49
+ % % @equiv format(Format, Args)
50
+ % % @param Format format string
51
+ % % @param Args format argument
52
+ % % @returns string
53
+ % % @doc Format string and data to a string.
54
+ % % Features most of OTP `io_lib:format/2'.
55
+ % % Raises `badarg' error if the number of format specifiers
56
+ % % does not match the length of the Args.
57
+ % % @end
58
+ % %-----------------------------------------------------------------------------
59
+ fwrite (Format , Args ) ->
60
+ format (Format , Args ).
31
61
32
62
% %-----------------------------------------------------------------------------
33
63
% % @param Format format string
34
64
% % @param Args format argument
35
65
% % @returns string
36
66
% % @doc Format string and data to a string.
37
- % % Approximates features of OTP io_lib:format/2, but
38
- % % only supports ~p and ~n format specifiers.
67
+ % % Features most of OTP `io_lib:format/2'.
39
68
% % Raises `badarg' error if the number of format specifiers
40
69
% % does not match the length of the Args.
41
70
% % @end
42
71
% %-----------------------------------------------------------------------------
43
- -spec format (Format :: string (), Args :: list ()) -> string ().
72
+ -spec format (Format :: io :format (), Args :: list ()) -> string ().
73
+ format (Format , Args ) when is_binary (Format ) ->
74
+ format (binary_to_list (Format ), Args );
75
+ format (Format , Args ) when is_atom (Format ) ->
76
+ format (atom_to_list (Format ), Args );
44
77
format (Format , Args ) ->
45
78
{FormatTokens , Instr } = split (Format ),
46
79
case length (FormatTokens ) == length (Args ) + 1 of
@@ -236,11 +269,7 @@ format_string(Format, T) ->
236
269
format_spw (# format {control = s }, T ) when is_atom (T ) ->
237
270
erlang :atom_to_list (T );
238
271
format_spw (_Format , T ) when is_atom (T ) ->
239
- AtomStr = erlang :atom_to_list (T ),
240
- case atom_requires_quotes (T , AtomStr ) of
241
- false -> AtomStr ;
242
- true -> [$' , AtomStr , $' ]
243
- end ;
272
+ write_atom (T );
244
273
format_spw (# format {control = s , mod = t } = Format , T ) when is_binary (T ) ->
245
274
case unicode :characters_to_list (T , utf8 ) of
246
275
L when is_list (L ) -> L ;
@@ -254,8 +283,8 @@ format_spw(#format{control = Control, mod = t} = Format, T) when is_binary(T) ->
254
283
L when is_list (L ) ->
255
284
FormattedStr =
256
285
case {Control , test_string_class (L )} of
257
- {p , latin1_printable } -> format_p_string (L , [] );
258
- {p , unicode } -> [format_p_string (L , [] ), " /utf8" ];
286
+ {p , latin1_printable } -> write_string (L , $" );
287
+ {p , unicode } -> [write_string (L , $" ), " /utf8" ];
259
288
_ -> lists :join ($, , [integer_to_list (B ) || B <- L ])
260
289
end ,
261
290
[$< , $< , FormattedStr , $> , $> ];
@@ -266,7 +295,7 @@ format_spw(#format{control = Control, mod = undefined}, T) when is_binary(T) ->
266
295
L = erlang :binary_to_list (T ),
267
296
FormattedStr =
268
297
case {Control , test_string_class (L )} of
269
- {p , latin1_printable } -> format_p_string (L , [] );
298
+ {p , latin1_printable } -> write_string (L , $" );
270
299
_ -> lists :join ($, , [integer_to_list (B ) || B <- L ])
271
300
end ,
272
301
[$< , $< , FormattedStr , $> , $> ];
@@ -279,7 +308,7 @@ format_spw(#format{control = s, mod = Mod}, L) when is_list(L) ->
279
308
end ;
280
309
format_spw (# format {control = p } = Format , L ) when is_list (L ) ->
281
310
case test_string_class (L ) of
282
- latin1_printable -> format_p_string (L , [] );
311
+ latin1_printable -> write_string (L , $" );
283
312
_ -> [$[ , lists :join ($, , [format_spw (Format , E ) || E <- L ]), $] ]
284
313
end ;
285
314
format_spw (# format {control = w } = Format , L ) when is_list (L ) ->
@@ -314,7 +343,6 @@ format_spw(#format{mod = Mod} = Format, T) when is_map(T) ->
314
343
$}
315
344
].
316
345
317
- % % We will probably need to add 'maybe' with OTP 27
318
346
-define (RESERVED_KEYWORDS , [
319
347
'after' ,
320
348
'and' ,
@@ -334,6 +362,7 @@ format_spw(#format{mod = Mod} = Format, T) when is_map(T) ->
334
362
'fun' ,
335
363
'if' ,
336
364
'let' ,
365
+ 'maybe' ,
337
366
'not' ,
338
367
'of' ,
339
368
'or' ,
@@ -345,23 +374,38 @@ format_spw(#format{mod = Mod} = Format, T) when is_map(T) ->
345
374
'xor'
346
375
]).
347
376
348
- % % @private
349
- atom_requires_quotes (Atom , AtomStr ) ->
377
+ -spec write_atom (Atom :: atom ()) -> chars ().
378
+ write_atom (Atom ) ->
379
+ AtomStr = erlang :atom_to_list (Atom ),
350
380
case lists :member (Atom , ? RESERVED_KEYWORDS ) of
351
- true -> true ;
352
- false -> atom_requires_quotes0 (AtomStr )
381
+ true -> [ $' , AtomStr , $' ] ;
382
+ false -> write_atom_maybe_quote_escape (AtomStr )
353
383
end .
354
384
355
- atom_requires_quotes0 ([C | _T ]) when C < $a orelse C > $z -> true ;
356
- atom_requires_quotes0 ([_C | T ]) -> atom_requires_quotes1 (T ).
357
-
358
- atom_requires_quotes1 ([]) -> false ;
359
- atom_requires_quotes1 ([$@ | T ]) -> atom_requires_quotes1 (T );
360
- atom_requires_quotes1 ([$_ | T ]) -> atom_requires_quotes1 (T );
361
- atom_requires_quotes1 ([C | T ]) when C >= $A andalso C =< $Z -> atom_requires_quotes1 (T );
362
- atom_requires_quotes1 ([C | T ]) when C >= $0 andalso C =< $9 -> atom_requires_quotes1 (T );
363
- atom_requires_quotes1 ([C | T ]) when C >= $a andalso C =< $z -> atom_requires_quotes1 (T );
364
- atom_requires_quotes1 (_ ) -> true .
385
+ % % @private
386
+ write_atom_maybe_quote_escape ([C | _T ] = AtomStr ) when C < $a orelse C > $z ->
387
+ write_atom_maybe_quote_escape (AtomStr , true , []);
388
+ write_atom_maybe_quote_escape (AtomStr ) ->
389
+ write_atom_maybe_quote_escape (AtomStr , false , []).
390
+
391
+ write_atom_maybe_quote_escape ([], false , Acc ) ->
392
+ lists :reverse (Acc );
393
+ write_atom_maybe_quote_escape ([], true , Acc ) ->
394
+ [$' | lists :reverse ([$' | Acc ])];
395
+ write_atom_maybe_quote_escape ([$@ | T ], Quote , Acc ) ->
396
+ write_atom_maybe_quote_escape (T , Quote , [$@ | Acc ]);
397
+ write_atom_maybe_quote_escape ([$_ | T ], Quote , Acc ) ->
398
+ write_atom_maybe_quote_escape (T , Quote , [$_ | Acc ]);
399
+ write_atom_maybe_quote_escape ([C | T ], Quote , Acc ) when C >= $A andalso C =< $Z ->
400
+ write_atom_maybe_quote_escape (T , Quote , [C | Acc ]);
401
+ write_atom_maybe_quote_escape ([C | T ], Quote , Acc ) when C >= $0 andalso C =< $9 ->
402
+ write_atom_maybe_quote_escape (T , Quote , [C | Acc ]);
403
+ write_atom_maybe_quote_escape ([C | T ], Quote , Acc ) when C >= $a andalso C =< $z ->
404
+ write_atom_maybe_quote_escape (T , Quote , [C | Acc ]);
405
+ write_atom_maybe_quote_escape ([$' | T ], _Quote , Acc ) ->
406
+ write_atom_maybe_quote_escape (T , true , [$' , $\\ | Acc ]);
407
+ write_atom_maybe_quote_escape ([C | T ], _Quote , Acc ) ->
408
+ write_atom_maybe_quote_escape (T , true , [C | Acc ]).
365
409
366
410
% % @private
367
411
format_integer (# format {control = C , precision = Precision0 }, T0 ) when
@@ -447,8 +491,8 @@ format_char(_, _) ->
447
491
% % @private
448
492
% % String classes:
449
493
% % latin1_printable
450
- % % latin1_unprintable
451
494
% % unicode
495
+ % % unprintable
452
496
% % io_lib doesn't distinguish between valid unicode and invalid unicode
453
497
% % characters. This is done with io, though, when actually writing the string.
454
498
% % Compare:
@@ -464,38 +508,91 @@ test_string_class([H | T], Class) when is_integer(H) andalso H >= 0 ->
464
508
case {Class , char_class (H )} of
465
509
{_ , latin1_printable } -> Class ;
466
510
{latin1_printable , CharClass } -> CharClass ;
467
- {_ , latin1_unprintable } -> Class ;
468
- {latin1_unprintable , CharClass } -> CharClass ;
469
- _ -> unicode
511
+ {_ , unicode } -> Class ;
512
+ {unicode , CharClass } -> CharClass ;
513
+ _ -> unprintable
470
514
end ,
471
515
test_string_class (T , NewClass );
472
516
test_string_class ([], Class ) ->
473
517
Class ;
474
518
test_string_class (_String , _Class ) ->
475
519
not_a_string .
476
520
477
- char_class (H ) when H >= 0 andalso H < 8 -> latin1_unprintable ;
521
+ char_class (H ) when H >= 0 andalso H < 8 -> unprintable ;
478
522
char_class (27 ) -> latin1_printable ;
479
- char_class (H ) when H >= 14 andalso H < 32 -> latin1_unprintable ;
523
+ char_class (H ) when H >= 14 andalso H < 32 -> unprintable ;
480
524
char_class (H ) when H < 256 -> latin1_printable ;
481
525
char_class (_H ) -> unicode .
482
526
527
+ % %-----------------------------------------------------------------------------
528
+ % % @equiv write_string(String, $")
529
+ % % @param String string to print
530
+ % % @doc Returns the list of characters needed to print String as a string.
531
+ % % @end
532
+ % %-----------------------------------------------------------------------------
533
+ -spec write_string (string ()) -> chars ().
534
+ write_string (String ) ->
535
+ write_string (String , $" ).
536
+
537
+ % % @private
538
+ -spec write_string (string (), char ()) -> chars ().
539
+ write_string (String , $" ) ->
540
+ format_p_string (String , $" , []).
541
+
542
+ % % @private
543
+ format_p_string ([], Q , Acc ) ->
544
+ [Q , lists :reverse (Acc ), Q ];
545
+ format_p_string ([8 | T ], Q , Acc ) ->
546
+ format_p_string (T , Q , [" \\ b" | Acc ]);
547
+ format_p_string ([9 | T ], Q , Acc ) ->
548
+ format_p_string (T , Q , [" \\ t" | Acc ]);
549
+ format_p_string ([10 | T ], Q , Acc ) ->
550
+ format_p_string (T , Q , [" \\ n" | Acc ]);
551
+ format_p_string ([11 | T ], Q , Acc ) ->
552
+ format_p_string (T , Q , [" \\ v" | Acc ]);
553
+ format_p_string ([12 | T ], Q , Acc ) ->
554
+ format_p_string (T , Q , [" \\ f" | Acc ]);
555
+ format_p_string ([13 | T ], Q , Acc ) ->
556
+ format_p_string (T , Q , [" \\ r" | Acc ]);
557
+ format_p_string ([27 | T ], Q , Acc ) ->
558
+ format_p_string (T , Q , [" \\ e" | Acc ]);
559
+ format_p_string ([Q | T ], Q , Acc ) ->
560
+ format_p_string (T , Q , [" \\ " , Q | Acc ]);
561
+ format_p_string ([H | T ], Q , Acc ) ->
562
+ format_p_string (T , Q , [H | Acc ]).
563
+
564
+ % %-----------------------------------------------------------------------------
565
+ % % @param List term to test
566
+ % % @doc Determine if `List' is a flat list of printable characters
567
+ % % @end
568
+ % %-----------------------------------------------------------------------------
569
+ -spec printable_list (List :: any ()) -> boolean ().
570
+ printable_list (List ) ->
571
+ StringClass = test_string_class (List ),
572
+ case {StringClass , io :printable_range ()} of
573
+ {not_a_string , _ } -> false ;
574
+ {unprintable , _ } -> false ;
575
+ {latin1_printable , _ } -> true ;
576
+ {unicode , unicode } -> true ;
577
+ {unicode , latin1 } -> false
578
+ end .
579
+
483
580
% % @private
484
- format_p_string ([], Acc ) ->
485
- [ $" , lists : reverse ( Acc ), $" ];
486
- format_p_string ([ 8 | T ], Acc ) ->
487
- format_p_string ( T , [ " \\ b " | Acc ]);
488
- format_p_string ([ 9 | T ], Acc ) ->
489
- format_p_string ( T , [ " \\ t " | Acc ]);
490
- format_p_string ([ 10 | T ], Acc ) ->
491
- format_p_string ( T , [ " \\ n " | Acc ]);
492
- format_p_string ([ 11 | T ], Acc ) ->
493
- format_p_string ( T , [ " \\ v " | Acc ]);
494
- format_p_string ([ 12 | T ], Acc ) ->
495
- format_p_string ( T , [ " \\ f " | Acc ]);
496
- format_p_string ([ 13 | T ], Acc ) ->
497
- format_p_string ( T , [ " \\ r " | Acc ]);
498
- format_p_string ([ 27 | T ], Acc ) ->
499
- format_p_string ( T , [ " \\ e " | Acc ]);
500
- format_p_string ([ H | T ], Acc ) ->
501
- format_p_string ( T , [H | Acc ]).
581
+ - spec chars_length ( chars ()) -> non_neg_integer ().
582
+ chars_length ( S ) ->
583
+ try
584
+ iolist_size ( S )
585
+ catch
586
+ _ : _ ->
587
+ string : length ( S )
588
+ end .
589
+
590
+ % %-----------------------------------------------------------------------------
591
+ % % @equiv forma("~w", [Term])
592
+ % % @param Term term to represent
593
+ % % @doc Returns a character list that represents Term
594
+ % % @end
595
+ % %-----------------------------------------------------------------------------
596
+ - spec write ( any ()) -> chars ().
597
+ write ( Term ) ->
598
+ format ( " ~w " , [Term ]).
0 commit comments